diff --git a/package.json b/package.json index 57e050058915f..10be133217a2c 100644 --- a/package.json +++ b/package.json @@ -283,7 +283,7 @@ "@types/d3": "^3.5.41", "@types/dedent": "^0.7.0", "@types/delete-empty": "^2.0.0", - "@types/elasticsearch": "^5.0.30", + "@types/elasticsearch": "^5.0.33", "@types/enzyme": "^3.1.12", "@types/eslint": "^4.16.6", "@types/execa": "^0.9.0", diff --git a/src/legacy/server/kbn_server.js b/src/legacy/server/kbn_server.js index c6396a8f009a9..78a8829dbb08a 100644 --- a/src/legacy/server/kbn_server.js +++ b/src/legacy/server/kbn_server.js @@ -36,7 +36,7 @@ import configCompleteMixin from './config/complete'; import optimizeMixin from '../../optimize'; import * as Plugins from './plugins'; import { indexPatternsMixin } from './index_patterns'; -import { savedObjectsMixin } from './saved_objects'; +import { savedObjectsMixin } from './saved_objects/saved_objects_mixin'; import { sampleDataMixin } from './sample_data'; import { capabilitiesMixin } from './capabilities'; import { urlShorteningMixin } from './url_shortening'; diff --git a/src/legacy/server/saved_objects/export/get_sorted_objects_for_export.test.ts b/src/legacy/server/saved_objects/export/get_sorted_objects_for_export.test.ts index 4122fa63585e0..7ac3ebd412c0a 100644 --- a/src/legacy/server/saved_objects/export/get_sorted_objects_for_export.test.ts +++ b/src/legacy/server/saved_objects/export/get_sorted_objects_for_export.test.ts @@ -18,18 +18,10 @@ */ import { getSortedObjectsForExport } from './get_sorted_objects_for_export'; +import { SavedObjectsClientMock } from '../service/saved_objects_client.mock'; describe('getSortedObjectsForExport()', () => { - const savedObjectsClient = { - errors: {} as any, - find: jest.fn(), - bulkGet: jest.fn(), - create: jest.fn(), - bulkCreate: jest.fn(), - delete: jest.fn(), - get: jest.fn(), - update: jest.fn(), - }; + const savedObjectsClient = SavedObjectsClientMock.create(); afterEach(() => { savedObjectsClient.find.mockReset(); @@ -48,8 +40,10 @@ describe('getSortedObjectsForExport()', () => { { id: '2', type: 'search', + attributes: {}, references: [ { + name: 'name', type: 'index-pattern', id: '1', }, @@ -58,9 +52,12 @@ describe('getSortedObjectsForExport()', () => { { id: '1', type: 'index-pattern', + attributes: {}, references: [], }, ], + per_page: 1, + page: 0, }); const response = await getSortedObjectsForExport({ savedObjectsClient, @@ -70,15 +67,18 @@ describe('getSortedObjectsForExport()', () => { expect(response).toMatchInlineSnapshot(` Array [ Object { + "attributes": Object {}, "id": "1", "references": Array [], "type": "index-pattern", }, Object { + "attributes": Object {}, "id": "2", "references": Array [ Object { "id": "1", + "name": "name", "type": "index-pattern", }, ], @@ -118,9 +118,11 @@ Array [ { id: '2', type: 'search', + attributes: {}, references: [ { type: 'index-pattern', + name: 'name', id: '1', }, ], @@ -128,9 +130,12 @@ Array [ { id: '1', type: 'index-pattern', + attributes: {}, references: [], }, ], + per_page: 1, + page: 0, }); await expect( getSortedObjectsForExport({ @@ -147,16 +152,19 @@ Array [ { id: '2', type: 'search', + attributes: {}, references: [ { - type: 'index-pattern', id: '1', + name: 'name', + type: 'index-pattern', }, ], }, { id: '1', type: 'index-pattern', + attributes: {}, references: [], }, ], @@ -179,15 +187,18 @@ Array [ expect(response).toMatchInlineSnapshot(` Array [ Object { + "attributes": Object {}, "id": "1", "references": Array [], "type": "index-pattern", }, Object { + "attributes": Object {}, "id": "2", "references": Array [ Object { "id": "1", + "name": "name", "type": "index-pattern", }, ], @@ -227,9 +238,11 @@ Array [ { id: '2', type: 'search', + attributes: {}, references: [ { type: 'index-pattern', + name: 'name', id: '1', }, ], @@ -241,6 +254,7 @@ Array [ { id: '1', type: 'index-pattern', + attributes: {}, references: [], }, ], @@ -260,15 +274,18 @@ Array [ expect(response).toMatchInlineSnapshot(` Array [ Object { + "attributes": Object {}, "id": "1", "references": Array [], "type": "index-pattern", }, Object { + "attributes": Object {}, "id": "2", "references": Array [ Object { "id": "1", + "name": "name", "type": "index-pattern", }, ], diff --git a/src/legacy/server/saved_objects/export/get_sorted_objects_for_export.ts b/src/legacy/server/saved_objects/export/get_sorted_objects_for_export.ts index dc48e35dc5898..9782de4b553f8 100644 --- a/src/legacy/server/saved_objects/export/get_sorted_objects_for_export.ts +++ b/src/legacy/server/saved_objects/export/get_sorted_objects_for_export.ts @@ -18,7 +18,7 @@ */ import Boom from 'boom'; -import { SavedObjectsClient } from '../service/saved_objects_client'; +import { SavedObjectsClientContract } from '../'; import { injectNestedDependencies } from './inject_nested_depdendencies'; import { sortObjects } from './sort_objects'; @@ -30,7 +30,7 @@ interface ObjectToExport { interface ExportObjectsOptions { types?: string[]; objects?: ObjectToExport[]; - savedObjectsClient: SavedObjectsClient; + savedObjectsClient: SavedObjectsClientContract; exportSizeLimit: number; includeReferencesDeep?: boolean; } @@ -44,7 +44,7 @@ async function fetchObjectsToExport({ objects?: ObjectToExport[]; types?: string[]; exportSizeLimit: number; - savedObjectsClient: SavedObjectsClient; + savedObjectsClient: SavedObjectsClientContract; }) { if (objects) { if (objects.length > exportSizeLimit) { diff --git a/src/legacy/server/saved_objects/export/inject_nested_depdendencies.ts b/src/legacy/server/saved_objects/export/inject_nested_depdendencies.ts index 8529dcae4f0a6..ee9ce781ef9a5 100644 --- a/src/legacy/server/saved_objects/export/inject_nested_depdendencies.ts +++ b/src/legacy/server/saved_objects/export/inject_nested_depdendencies.ts @@ -18,7 +18,7 @@ */ import Boom from 'boom'; -import { SavedObject, SavedObjectsClient } from '../service/saved_objects_client'; +import { SavedObject, SavedObjectsClientContract } from '../service/saved_objects_client'; export function getObjectReferencesToFetch(savedObjectsMap: Map) { const objectsToFetch = new Map(); @@ -34,7 +34,7 @@ export function getObjectReferencesToFetch(savedObjectsMap: Map(); for (const savedObject of savedObjects) { diff --git a/src/legacy/server/saved_objects/import/import_saved_objects.ts b/src/legacy/server/saved_objects/import/import_saved_objects.ts index 03be32392b803..10c1350c4c579 100644 --- a/src/legacy/server/saved_objects/import/import_saved_objects.ts +++ b/src/legacy/server/saved_objects/import/import_saved_objects.ts @@ -18,17 +18,17 @@ */ import { Readable } from 'stream'; -import { SavedObjectsClient } from '../service'; import { collectSavedObjects } from './collect_saved_objects'; import { extractErrors } from './extract_errors'; import { ImportError } from './types'; import { validateReferences } from './validate_references'; +import { SavedObjectsClientContract } from '../'; interface ImportSavedObjectsOptions { readStream: Readable; objectLimit: number; overwrite: boolean; - savedObjectsClient: SavedObjectsClient; + savedObjectsClient: SavedObjectsClientContract; supportedTypes: string[]; } diff --git a/src/legacy/server/saved_objects/import/resolve_import_errors.ts b/src/legacy/server/saved_objects/import/resolve_import_errors.ts index 77fe856ece9ff..5cd4d2fca740c 100644 --- a/src/legacy/server/saved_objects/import/resolve_import_errors.ts +++ b/src/legacy/server/saved_objects/import/resolve_import_errors.ts @@ -18,7 +18,7 @@ */ import { Readable } from 'stream'; -import { SavedObjectsClient } from '../service'; +import { SavedObjectsClientContract } from '../'; import { collectSavedObjects } from './collect_saved_objects'; import { createObjectsFilter } from './create_objects_filter'; import { extractErrors } from './extract_errors'; @@ -29,7 +29,7 @@ import { validateReferences } from './validate_references'; interface ResolveImportErrorsOptions { readStream: Readable; objectLimit: number; - savedObjectsClient: SavedObjectsClient; + savedObjectsClient: SavedObjectsClientContract; retries: Retry[]; supportedTypes: string[]; } diff --git a/src/legacy/server/saved_objects/import/validate_references.ts b/src/legacy/server/saved_objects/import/validate_references.ts index e44cacd992489..2e3c1ef5293b3 100644 --- a/src/legacy/server/saved_objects/import/validate_references.ts +++ b/src/legacy/server/saved_objects/import/validate_references.ts @@ -18,7 +18,7 @@ */ import Boom from 'boom'; -import { SavedObject, SavedObjectsClient } from '../service'; +import { SavedObject, SavedObjectsClientContract } from '../'; import { ImportError } from './types'; const REF_TYPES_TO_VLIDATE = ['index-pattern', 'search']; @@ -29,7 +29,7 @@ function filterReferencesToValidate({ type }: { type: string }) { export async function getNonExistingReferenceAsKeys( savedObjects: SavedObject[], - savedObjectsClient: SavedObjectsClient + savedObjectsClient: SavedObjectsClientContract ) { const collector = new Map(); // Collect all references within objects @@ -77,7 +77,7 @@ export async function getNonExistingReferenceAsKeys( export async function validateReferences( savedObjects: SavedObject[], - savedObjectsClient: SavedObjectsClient + savedObjectsClient: SavedObjectsClientContract ) { const errorMap: { [key: string]: ImportError } = {}; const nonExistingReferenceKeys = await getNonExistingReferenceAsKeys( diff --git a/src/legacy/server/saved_objects/index.js b/src/legacy/server/saved_objects/index.ts similarity index 85% rename from src/legacy/server/saved_objects/index.js rename to src/legacy/server/saved_objects/index.ts index e128322850a6e..e6e9e2d266000 100644 --- a/src/legacy/server/saved_objects/index.js +++ b/src/legacy/server/saved_objects/index.ts @@ -17,5 +17,8 @@ * under the License. */ -export { savedObjectsMixin } from './saved_objects_mixin'; -export { SavedObjectsClient } from './service'; +export * from './service'; + +export { SavedObjectsSchema } from './schema'; + +export { SavedObjectsManagement } from './management'; diff --git a/src/legacy/server/saved_objects/migrations/core/document_migrator.ts b/src/legacy/server/saved_objects/migrations/core/document_migrator.ts index 636b35e390ef0..bcff2988f4afe 100644 --- a/src/legacy/server/saved_objects/migrations/core/document_migrator.ts +++ b/src/legacy/server/saved_objects/migrations/core/document_migrator.ts @@ -64,7 +64,8 @@ import Boom from 'boom'; import _ from 'lodash'; import cloneDeep from 'lodash.clonedeep'; import Semver from 'semver'; -import { MigrationVersion, RawSavedObjectDoc } from '../../serialization'; +import { RawSavedObjectDoc } from '../../serialization'; +import { MigrationVersion } from '../../'; import { LogFn, Logger, MigrationLogger } from './migration_logger'; export type TransformFn = (doc: RawSavedObjectDoc, log?: Logger) => RawSavedObjectDoc; diff --git a/src/legacy/server/saved_objects/migrations/core/elastic_index.ts b/src/legacy/server/saved_objects/migrations/core/elastic_index.ts index 48d44492eb275..1e55bd3d01688 100644 --- a/src/legacy/server/saved_objects/migrations/core/elastic_index.ts +++ b/src/legacy/server/saved_objects/migrations/core/elastic_index.ts @@ -24,12 +24,9 @@ import _ from 'lodash'; import { IndexMapping } from '../../../mappings'; -import { MigrationVersion } from '../../serialization'; +import { MigrationVersion } from '../../'; import { AliasAction, CallCluster, NotFound, RawDoc, ShardsInfo } from './call_cluster'; -// @ts-ignore untyped dependency -import { getTypes } from '../../../mappings'; - const settings = { number_of_shards: 1, auto_expand_replicas: '0-1' }; export interface FullIndexInfo { diff --git a/src/legacy/server/saved_objects/routes/bulk_create.test.ts b/src/legacy/server/saved_objects/routes/bulk_create.test.ts index 5c69c490084b3..f981b0a62f605 100644 --- a/src/legacy/server/saved_objects/routes/bulk_create.test.ts +++ b/src/legacy/server/saved_objects/routes/bulk_create.test.ts @@ -20,22 +20,14 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createBulkCreateRoute } from './bulk_create'; +import { SavedObjectsClientMock } from '../service/saved_objects_client.mock'; describe('POST /api/saved_objects/_bulk_create', () => { let server: Hapi.Server; - const savedObjectsClient = { - errors: {} as any, - bulkCreate: jest.fn(), - bulkGet: jest.fn(), - create: jest.fn(), - delete: jest.fn(), - find: jest.fn(), - get: jest.fn(), - update: jest.fn(), - }; + const savedObjectsClient = SavedObjectsClientMock.create(); beforeEach(() => { - savedObjectsClient.bulkCreate.mockImplementation(() => Promise.resolve('')); + savedObjectsClient.bulkCreate.mockImplementation(() => Promise.resolve('' as any)); server = createMockServer(); const prereqs = { @@ -75,7 +67,8 @@ describe('POST /api/saved_objects/_bulk_create', () => { id: 'abc123', type: 'index-pattern', title: 'logstash-*', - version: 2, + attributes: {}, + version: '2', references: [], }, ], diff --git a/src/legacy/server/saved_objects/routes/bulk_create.ts b/src/legacy/server/saved_objects/routes/bulk_create.ts index ffc6831bc3e5f..35b90ecbcd853 100644 --- a/src/legacy/server/saved_objects/routes/bulk_create.ts +++ b/src/legacy/server/saved_objects/routes/bulk_create.ts @@ -19,7 +19,7 @@ import Hapi from 'hapi'; import Joi from 'joi'; -import { SavedObjectAttributes, SavedObjectsClient } from '../'; +import { SavedObjectAttributes, SavedObjectsClientContract } from '../'; import { Prerequisites, SavedObjectReference, WithoutQueryAndParams } from './types'; interface SavedObject { @@ -33,7 +33,7 @@ interface SavedObject { interface BulkCreateRequest extends WithoutQueryAndParams { pre: { - savedObjectsClient: SavedObjectsClient; + savedObjectsClient: SavedObjectsClientContract; }; query: { overwrite: boolean; diff --git a/src/legacy/server/saved_objects/routes/types.ts b/src/legacy/server/saved_objects/routes/types.ts index 8a63a5ff32d9a..f658a61117890 100644 --- a/src/legacy/server/saved_objects/routes/types.ts +++ b/src/legacy/server/saved_objects/routes/types.ts @@ -18,7 +18,7 @@ */ import Hapi from 'hapi'; -import { SavedObjectsClient } from '../'; +import { SavedObjectsClientContract } from '../'; export interface SavedObjectReference { name: string; @@ -29,7 +29,7 @@ export interface SavedObjectReference { export interface Prerequisites { getSavedObjectsClient: { assign: string; - method: (req: Hapi.Request) => SavedObjectsClient; + method: (req: Hapi.Request) => SavedObjectsClientContract; }; } diff --git a/src/legacy/server/saved_objects/schema/schema.mock.ts b/src/legacy/server/saved_objects/schema/schema.mock.ts index 844893156deef..7fec7d54294d6 100644 --- a/src/legacy/server/saved_objects/schema/schema.mock.ts +++ b/src/legacy/server/saved_objects/schema/schema.mock.ts @@ -22,6 +22,7 @@ import { SavedObjectsSchema } from './schema'; type Schema = PublicMethodsOf; const createSchemaMock = () => { const mocked: jest.Mocked = { + getIndexForType: jest.fn().mockReturnValue('.kibana-test'), isHiddenType: jest.fn().mockReturnValue(false), isNamespaceAgnostic: jest.fn((type: string) => type === 'global'), }; diff --git a/src/legacy/server/saved_objects/schema/schema.ts b/src/legacy/server/saved_objects/schema/schema.ts index 14a613835569d..6756feeb15a0f 100644 --- a/src/legacy/server/saved_objects/schema/schema.ts +++ b/src/legacy/server/saved_objects/schema/schema.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - interface SavedObjectsSchemaTypeDefinition { isNamespaceAgnostic: boolean; hidden?: boolean; @@ -41,6 +40,14 @@ export class SavedObjectsSchema { return false; } + public getIndexForType(type: string): string | undefined { + if (this.definition != null && this.definition.hasOwnProperty(type)) { + return this.definition[type].indexPattern; + } else { + return undefined; + } + } + public isNamespaceAgnostic(type: string) { // if no plugins have registered a uiExports.savedObjectSchemas, // this.schema will be undefined, and no types are namespace agnostic diff --git a/src/legacy/server/saved_objects/serialization/index.ts b/src/legacy/server/saved_objects/serialization/index.ts index 997c5d38b69db..72071ae8866fa 100644 --- a/src/legacy/server/saved_objects/serialization/index.ts +++ b/src/legacy/server/saved_objects/serialization/index.ts @@ -27,6 +27,7 @@ import uuid from 'uuid'; import { SavedObjectsSchema } from '../schema'; import { decodeVersion, encodeVersion } from '../version'; +import { MigrationVersion, SavedObjectReference } from '../service/saved_objects_client'; /** * A raw document as represented directly in the saved object index. @@ -39,23 +40,6 @@ export interface RawDoc { _primary_term?: number; } -/** - * A dictionary of saved object type -> version used to determine - * what migrations need to be applied to a saved object. - */ -export interface MigrationVersion { - [type: string]: string; -} - -/** - * A reference object to anohter saved object. - */ -export interface SavedObjectReference { - name: string; - type: string; - id: string; -} - /** * A saved object type definition that allows for miscellaneous, unknown * properties, as current discussions around security, ACLs, etc indicate @@ -64,12 +48,12 @@ export interface SavedObjectReference { */ interface SavedObjectDoc { attributes: object; - id: string; + id?: string; // NOTE: SavedObjectDoc is used for uncreated objects where `id` is optional type: string; namespace?: string; migrationVersion?: MigrationVersion; version?: string; - updated_at?: Date; + updated_at?: string; [rootProp: string]: any; } diff --git a/src/legacy/server/saved_objects/service/index.js b/src/legacy/server/saved_objects/service/index.js deleted file mode 100644 index 4624197e323e3..0000000000000 --- a/src/legacy/server/saved_objects/service/index.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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 { SavedObjectsClient } from './saved_objects_client'; -export { SavedObjectsRepository, ScopedSavedObjectsClientProvider } from './lib'; diff --git a/src/legacy/server/saved_objects/service/index.d.ts b/src/legacy/server/saved_objects/service/index.ts similarity index 83% rename from src/legacy/server/saved_objects/service/index.d.ts rename to src/legacy/server/saved_objects/service/index.ts index cfc190bb199aa..c4e0d66eb95b8 100644 --- a/src/legacy/server/saved_objects/service/index.d.ts +++ b/src/legacy/server/saved_objects/service/index.ts @@ -31,15 +31,10 @@ export interface SavedObjectsService { getSavedObjectsRepository(...rest: any[]): any; } -export { SavedObjectsClientWrapperFactory } from './lib'; export { - FindOptions, - GetResponse, - UpdateResponse, - CreateResponse, - MigrationVersion, - SavedObject, - SavedObjectAttributes, - SavedObjectsClient, - SavedObjectReference, -} from './saved_objects_client'; + SavedObjectsRepository, + ScopedSavedObjectsClientProvider, + SavedObjectsClientWrapperFactory, +} from './lib'; + +export * from './saved_objects_client'; diff --git a/src/legacy/server/saved_objects/service/lib/decorate_es_error.test.js b/src/legacy/server/saved_objects/service/lib/decorate_es_error.test.ts similarity index 100% rename from src/legacy/server/saved_objects/service/lib/decorate_es_error.test.js rename to src/legacy/server/saved_objects/service/lib/decorate_es_error.test.ts index 8d070e1713202..272a26327b808 100644 --- a/src/legacy/server/saved_objects/service/lib/decorate_es_error.test.js +++ b/src/legacy/server/saved_objects/service/lib/decorate_es_error.test.ts @@ -21,13 +21,13 @@ import { errors as esErrors } from 'elasticsearch'; import { decorateEsError } from './decorate_es_error'; import { - isEsUnavailableError, + isBadRequestError, isConflictError, - isNotAuthorizedError, + isEsUnavailableError, isForbiddenError, - isRequestEntityTooLargeError, + isNotAuthorizedError, isNotFoundError, - isBadRequestError, + isRequestEntityTooLargeError, } from './errors'; describe('savedObjectsClient/decorateEsError', () => { diff --git a/src/legacy/server/saved_objects/service/lib/decorate_es_error.js b/src/legacy/server/saved_objects/service/lib/decorate_es_error.ts similarity index 93% rename from src/legacy/server/saved_objects/service/lib/decorate_es_error.js rename to src/legacy/server/saved_objects/service/lib/decorate_es_error.ts index c99ca7c794584..becb41b78dad4 100644 --- a/src/legacy/server/saved_objects/service/lib/decorate_es_error.js +++ b/src/legacy/server/saved_objects/service/lib/decorate_es_error.ts @@ -26,30 +26,33 @@ const { NoConnections, RequestTimeout, Conflict, + // @ts-ignore 401: NotAuthorized, + // @ts-ignore 403: Forbidden, + // @ts-ignore 413: RequestEntityTooLarge, NotFound, BadRequest, } = elasticsearch.errors; import { - decorateBadRequestError, - decorateNotAuthorizedError, - decorateForbiddenError, - decorateRequestEntityTooLargeError, createGenericNotFoundError, + decorateBadRequestError, decorateConflictError, decorateEsUnavailableError, + decorateForbiddenError, decorateGeneralError, + decorateNotAuthorizedError, + decorateRequestEntityTooLargeError, } from './errors'; -export function decorateEsError(error) { +export function decorateEsError(error: Error) { if (!(error instanceof Error)) { throw new Error('Expected an instance of Error'); } - const { reason } = get(error, 'body.error', {}); + const { reason } = get(error, 'body.error', { reason: undefined }); if ( error instanceof ConnectionFault || error instanceof ServiceUnavailable || diff --git a/src/legacy/server/saved_objects/service/lib/errors.d.ts b/src/legacy/server/saved_objects/service/lib/errors.d.ts deleted file mode 100644 index cfeed417b98da..0000000000000 --- a/src/legacy/server/saved_objects/service/lib/errors.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 function isBadRequestError(maybeError: any): boolean; -export function isNotAuthorizedError(maybeError: any): boolean; -export function isForbiddenError(maybeError: any): boolean; -export function isRequestEntityTooLargeError(maybeError: any): boolean; -export function isNotFoundError(maybeError: any): boolean; -export function isConflictError(maybeError: any): boolean; -export function isEsUnavailableError(maybeError: any): boolean; -export function isEsAutoCreateIndexError(maybeError: any): boolean; - -export function createInvalidVersionError(version: any): Error; -export function isInvalidVersionError(maybeError: Error): boolean; diff --git a/src/legacy/server/saved_objects/service/lib/errors.test.js b/src/legacy/server/saved_objects/service/lib/errors.test.ts similarity index 99% rename from src/legacy/server/saved_objects/service/lib/errors.test.js rename to src/legacy/server/saved_objects/service/lib/errors.test.ts index b9b437f345d7d..facbacae84d07 100644 --- a/src/legacy/server/saved_objects/service/lib/errors.test.js +++ b/src/legacy/server/saved_objects/service/lib/errors.test.ts @@ -21,22 +21,22 @@ import Boom from 'boom'; import { createBadRequestError, + createEsAutoCreateIndexError, + createGenericNotFoundError, createUnsupportedTypeError, decorateBadRequestError, - isBadRequestError, - decorateNotAuthorizedError, - isNotAuthorizedError, - decorateForbiddenError, - isForbiddenError, - createGenericNotFoundError, - isNotFoundError, decorateConflictError, - isConflictError, decorateEsUnavailableError, - isEsUnavailableError, + decorateForbiddenError, decorateGeneralError, + decorateNotAuthorizedError, + isBadRequestError, + isConflictError, isEsAutoCreateIndexError, - createEsAutoCreateIndexError, + isEsUnavailableError, + isForbiddenError, + isNotAuthorizedError, + isNotFoundError, } from './errors'; describe('savedObjectsClient/errorTypes', () => { @@ -354,6 +354,7 @@ describe('savedObjectsClient/errorTypes', () => { describe('createEsAutoCreateIndexError', () => { it('does not take an error argument', () => { const error = new Error(); + // @ts-ignore expect(createEsAutoCreateIndexError(error)).not.toBe(error); }); diff --git a/src/legacy/server/saved_objects/service/lib/errors.js b/src/legacy/server/saved_objects/service/lib/errors.ts similarity index 53% rename from src/legacy/server/saved_objects/service/lib/errors.js rename to src/legacy/server/saved_objects/service/lib/errors.ts index 63fed3f092bcc..e1df155022b11 100644 --- a/src/legacy/server/saved_objects/service/lib/errors.js +++ b/src/legacy/server/saved_objects/service/lib/errors.ts @@ -21,7 +21,16 @@ import Boom from 'boom'; const code = Symbol('SavedObjectsClientErrorCode'); -function decorate(error, errorCode, statusCode, message) { +interface DecoratedError extends Boom { + [code]?: string; +} + +function decorate( + error: Error | DecoratedError, + errorCode: string, + statusCode: number, + message?: string +): DecoratedError { if (isSavedObjectsClientError(error)) { return error; } @@ -30,112 +39,113 @@ function decorate(error, errorCode, statusCode, message) { statusCode, message, override: false, - }); + }) as DecoratedError; boom[code] = errorCode; return boom; } -export function isSavedObjectsClientError(error) { - return error && !!error[code]; +export function isSavedObjectsClientError(error: any): error is DecoratedError { + return Boolean(error && error[code]); } // 400 - badRequest const CODE_BAD_REQUEST = 'SavedObjectsClient/badRequest'; -export function decorateBadRequestError(error, reason) { +export function decorateBadRequestError(error: Error, reason?: string) { return decorate(error, CODE_BAD_REQUEST, 400, reason); } -export function createBadRequestError(reason) { +export function createBadRequestError(reason?: string) { return decorateBadRequestError(new Error('Bad Request'), reason); } -export function createUnsupportedTypeError(type) { +export function createUnsupportedTypeError(type: string) { return createBadRequestError(`Unsupported saved object type: '${type}'`); } -export function isBadRequestError(error) { - return error && error[code] === CODE_BAD_REQUEST; +export function isBadRequestError(error: Error | DecoratedError) { + return isSavedObjectsClientError(error) && error[code] === CODE_BAD_REQUEST; } // 400 - invalid version const CODE_INVALID_VERSION = 'SavedObjectsClient/invalidVersion'; -export function createInvalidVersionError(versionInput) { +export function createInvalidVersionError(versionInput?: string) { return decorate(Boom.badRequest(`Invalid version [${versionInput}]`), CODE_INVALID_VERSION, 400); } -export function isInvalidVersionError(error) { - return error && error[code] === CODE_INVALID_VERSION; +export function isInvalidVersionError(error: Error | DecoratedError) { + return isSavedObjectsClientError(error) && error[code] === CODE_INVALID_VERSION; } // 401 - Not Authorized const CODE_NOT_AUTHORIZED = 'SavedObjectsClient/notAuthorized'; -export function decorateNotAuthorizedError(error, reason) { +export function decorateNotAuthorizedError(error: Error, reason?: string) { return decorate(error, CODE_NOT_AUTHORIZED, 401, reason); } -export function isNotAuthorizedError(error) { - return error && error[code] === CODE_NOT_AUTHORIZED; +export function isNotAuthorizedError(error: Error | DecoratedError) { + return isSavedObjectsClientError(error) && error[code] === CODE_NOT_AUTHORIZED; } // 403 - Forbidden const CODE_FORBIDDEN = 'SavedObjectsClient/forbidden'; -export function decorateForbiddenError(error, reason) { +export function decorateForbiddenError(error: Error, reason?: string) { return decorate(error, CODE_FORBIDDEN, 403, reason); } -export function isForbiddenError(error) { - return error && error[code] === CODE_FORBIDDEN; +export function isForbiddenError(error: Error | DecoratedError) { + return isSavedObjectsClientError(error) && error[code] === CODE_FORBIDDEN; } // 413 - Request Entity Too Large const CODE_REQUEST_ENTITY_TOO_LARGE = 'SavedObjectsClient/requestEntityTooLarge'; -export function decorateRequestEntityTooLargeError(error, reason) { +export function decorateRequestEntityTooLargeError(error: Error, reason?: string) { return decorate(error, CODE_REQUEST_ENTITY_TOO_LARGE, 413, reason); } -export function isRequestEntityTooLargeError(error) { - return error && error[code] === CODE_REQUEST_ENTITY_TOO_LARGE; +export function isRequestEntityTooLargeError(error: Error | DecoratedError) { + return isSavedObjectsClientError(error) && error[code] === CODE_REQUEST_ENTITY_TOO_LARGE; } // 404 - Not Found const CODE_NOT_FOUND = 'SavedObjectsClient/notFound'; -export function createGenericNotFoundError(type = null, id = null) { +export function createGenericNotFoundError(type: string | null = null, id: string | null = null) { if (type && id) { return decorate(Boom.notFound(`Saved object [${type}/${id}] not found`), CODE_NOT_FOUND, 404); } return decorate(Boom.notFound(), CODE_NOT_FOUND, 404); } -export function isNotFoundError(error) { - return error && error[code] === CODE_NOT_FOUND; +export function isNotFoundError(error: Error | DecoratedError) { + return isSavedObjectsClientError(error) && error[code] === CODE_NOT_FOUND; } // 409 - Conflict const CODE_CONFLICT = 'SavedObjectsClient/conflict'; -export function decorateConflictError(error, reason) { +export function decorateConflictError(error: Error, reason?: string) { return decorate(error, CODE_CONFLICT, 409, reason); } -export function isConflictError(error) { - return error && error[code] === CODE_CONFLICT; +export function isConflictError(error: Error | DecoratedError) { + return isSavedObjectsClientError(error) && error[code] === CODE_CONFLICT; } // 503 - Es Unavailable const CODE_ES_UNAVAILABLE = 'SavedObjectsClient/esUnavailable'; -export function decorateEsUnavailableError(error, reason) { +export function decorateEsUnavailableError(error: Error, reason?: string) { return decorate(error, CODE_ES_UNAVAILABLE, 503, reason); } -export function isEsUnavailableError(error) { - return error && error[code] === CODE_ES_UNAVAILABLE; +export function isEsUnavailableError(error: Error | DecoratedError) { + return isSavedObjectsClientError(error) && error[code] === CODE_ES_UNAVAILABLE; } // 503 - Unable to automatically create index because of action.auto_create_index setting const CODE_ES_AUTO_CREATE_INDEX_ERROR = 'SavedObjectsClient/autoCreateIndex'; export function createEsAutoCreateIndexError() { const error = Boom.serverUnavailable('Automatic index creation failed'); - error.output.payload.code = 'ES_AUTO_CREATE_INDEX_ERROR'; + error.output.payload.attributes = error.output.payload.attributes || {}; + error.output.payload.attributes.code = 'ES_AUTO_CREATE_INDEX_ERROR'; return decorate(error, CODE_ES_AUTO_CREATE_INDEX_ERROR, 503); } -export function isEsAutoCreateIndexError(error) { - return error && error[code] === CODE_ES_AUTO_CREATE_INDEX_ERROR; +export function isEsAutoCreateIndexError(error: Error | DecoratedError) { + return isSavedObjectsClientError(error) && error[code] === CODE_ES_AUTO_CREATE_INDEX_ERROR; } // 500 - General Error const CODE_GENERAL_ERROR = 'SavedObjectsClient/generalError'; -export function decorateGeneralError(error, reason) { +export function decorateGeneralError(error: Error, reason?: string) { return decorate(error, CODE_GENERAL_ERROR, 500, reason); } diff --git a/src/legacy/server/saved_objects/service/lib/included_fields.test.js b/src/legacy/server/saved_objects/service/lib/included_fields.test.ts similarity index 79% rename from src/legacy/server/saved_objects/service/lib/included_fields.test.js rename to src/legacy/server/saved_objects/service/lib/included_fields.test.ts index d0b01638aff1a..40d6552c2ad5f 100644 --- a/src/legacy/server/saved_objects/service/lib/included_fields.test.js +++ b/src/legacy/server/saved_objects/service/lib/included_fields.test.ts @@ -24,12 +24,61 @@ describe('includedFields', () => { expect(includedFields()).toBe(undefined); }); - it('includes type', () => { + it('accepts type string', () => { const fields = includedFields('config', 'foo'); expect(fields).toHaveLength(7); expect(fields).toContain('type'); }); + it('accepts type as string array', () => { + const fields = includedFields(['config', 'secret'], 'foo'); + expect(fields).toMatchInlineSnapshot(` +Array [ + "config.foo", + "secret.foo", + "namespace", + "type", + "references", + "migrationVersion", + "updated_at", + "foo", +] +`); + }); + + it('accepts field as string', () => { + const fields = includedFields('config', 'foo'); + expect(fields).toHaveLength(7); + expect(fields).toContain('config.foo'); + }); + + it('accepts fields as an array', () => { + const fields = includedFields('config', ['foo', 'bar']); + + expect(fields).toHaveLength(9); + expect(fields).toContain('config.foo'); + expect(fields).toContain('config.bar'); + }); + + it('accepts type as string array and fields as string array', () => { + const fields = includedFields(['config', 'secret'], ['foo', 'bar']); + expect(fields).toMatchInlineSnapshot(` +Array [ + "config.foo", + "config.bar", + "secret.foo", + "secret.bar", + "namespace", + "type", + "references", + "migrationVersion", + "updated_at", + "foo", + "bar", +] +`); + }); + it('includes namespace', () => { const fields = includedFields('config', 'foo'); expect(fields).toHaveLength(7); @@ -54,20 +103,6 @@ describe('includedFields', () => { expect(fields).toContain('updated_at'); }); - it('accepts field as string', () => { - const fields = includedFields('config', 'foo'); - expect(fields).toHaveLength(7); - expect(fields).toContain('config.foo'); - }); - - it('accepts fields as an array', () => { - const fields = includedFields('config', ['foo', 'bar']); - - expect(fields).toHaveLength(9); - expect(fields).toContain('config.foo'); - expect(fields).toContain('config.bar'); - }); - it('uses wildcard when type is not provided', () => { const fields = includedFields(undefined, 'foo'); expect(fields).toHaveLength(7); diff --git a/src/legacy/server/saved_objects/service/lib/included_fields.js b/src/legacy/server/saved_objects/service/lib/included_fields.ts similarity index 69% rename from src/legacy/server/saved_objects/service/lib/included_fields.js rename to src/legacy/server/saved_objects/service/lib/included_fields.ts index ce972d89afeae..f372db5a1a635 100644 --- a/src/legacy/server/saved_objects/service/lib/included_fields.js +++ b/src/legacy/server/saved_objects/service/lib/included_fields.ts @@ -17,22 +17,25 @@ * under the License. */ +function toArray(value: string | string[]): string[] { + return typeof value === 'string' ? [value] : value; +} /** * Provides an array of paths for ES source filtering - * - * @param {string} type - * @param {string|array} fields - * @returns {array} */ -export function includedFields(type, fields) { - if (!fields || fields.length === 0) return; +export function includedFields(type: string | string[] = '*', fields?: string[] | string) { + if (!fields || fields.length === 0) { + return; + } // convert to an array - const sourceFields = typeof fields === 'string' ? [fields] : fields; - const sourceType = type || '*'; + const sourceFields = toArray(fields); + const sourceType = toArray(type); - return sourceFields - .map(f => `${sourceType}.${f}`) + return sourceType + .reduce((acc: string[], t) => { + return [...acc, ...sourceFields.map(f => `${t}.${f}`)]; + }, []) .concat('namespace') .concat('type') .concat('references') diff --git a/src/legacy/server/saved_objects/service/lib/index.js b/src/legacy/server/saved_objects/service/lib/index.js deleted file mode 100644 index 5851fc8568e4c..0000000000000 --- a/src/legacy/server/saved_objects/service/lib/index.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 { SavedObjectsRepository } from './repository'; -export { ScopedSavedObjectsClientProvider } from './scoped_client_provider'; - -import * as errors from './errors'; -export { errors }; diff --git a/src/legacy/server/saved_objects/service/lib/index.d.ts b/src/legacy/server/saved_objects/service/lib/index.ts similarity index 96% rename from src/legacy/server/saved_objects/service/lib/index.d.ts rename to src/legacy/server/saved_objects/service/lib/index.ts index 486d3b1b46bb1..68fa240584100 100644 --- a/src/legacy/server/saved_objects/service/lib/index.d.ts +++ b/src/legacy/server/saved_objects/service/lib/index.ts @@ -17,14 +17,12 @@ * under the License. */ -import errors from './errors'; - -export { errors }; - export { SavedObjectsRepository, SavedObjectsRepositoryOptions } from './repository'; - export { SavedObjectsClientWrapperFactory, SavedObjectsClientWrapperOptions, ScopedSavedObjectsClientProvider, } from './scoped_client_provider'; + +import * as errors from './errors'; +export { errors }; diff --git a/src/legacy/server/saved_objects/service/lib/repository.d.ts b/src/legacy/server/saved_objects/service/lib/repository.d.ts deleted file mode 100644 index af2f7d04126f2..0000000000000 --- a/src/legacy/server/saved_objects/service/lib/repository.d.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 { BaseOptions, SavedObject } from '../saved_objects_client'; - -export interface SavedObjectsRepositoryOptions { - index: string | string[]; - mappings: unknown; - callCluster: unknown; - schema: unknown; - serializer: unknown; - migrator: unknown; - onBeforeWrite?: ( - action: 'create' | 'index' | 'update' | 'bulk' | 'delete' | 'deleteByQuery', - params: { - index: string; - id?: string; - body: any; - } - ) => void; - onBeforeRead?: ( - action: 'get' | 'bulk', - params: { - index: string; - id?: string; - body: any; - } - ) => void; -} - -export declare class SavedObjectsRepository { - // ATTENTION: this interface is incomplete - - public get: (type: string, id: string, options?: BaseOptions) => Promise; - public incrementCounter: ( - type: string, - id: string, - counterFieldName: string, - options?: BaseOptions - ) => Promise; - - constructor(options: SavedObjectsRepositoryOptions); -} diff --git a/src/legacy/server/saved_objects/service/lib/repository.test.js b/src/legacy/server/saved_objects/service/lib/repository.test.js index 37941bcc1814e..29ccdb3b8002a 100644 --- a/src/legacy/server/saved_objects/service/lib/repository.test.js +++ b/src/legacy/server/saved_objects/service/lib/repository.test.js @@ -1106,7 +1106,7 @@ describe('SavedObjectsRepository', () => { expect(callAdminCluster).toHaveBeenCalledWith('deleteByQuery', { body: { conflicts: 'proceed' }, ignore: [404], - index: ['beats', '.kibana-test'], + index: ['.kibana-test', 'beats'], refresh: 'wait_for', }); }); @@ -1160,7 +1160,7 @@ describe('SavedObjectsRepository', () => { namespace: 'foo-namespace', search: 'foo*', searchFields: ['foo'], - type: 'bar', + type: ['bar'], sortField: 'name', sortOrder: 'desc', defaultSearchOperator: 'AND', @@ -1560,6 +1560,90 @@ describe('SavedObjectsRepository', () => { error: { statusCode: 404, message: 'Not found' }, }); }); + + it('returns errors when requesting unsupported types', async () => { + callAdminCluster.mockResolvedValue({ + docs: [ + { + _type: '_doc', + _id: 'one', + found: true, + ...mockVersionProps, + _source: { ...mockTimestampFields, config: { title: 'Test1' } }, + }, + { + _type: '_doc', + _id: 'three', + found: true, + ...mockVersionProps, + _source: { ...mockTimestampFields, config: { title: 'Test3' } }, + }, + { + _type: '_doc', + _id: 'five', + found: true, + ...mockVersionProps, + _source: { ...mockTimestampFields, config: { title: 'Test5' } }, + }, + ], + }); + + const { saved_objects: savedObjects } = await savedObjectsRepository.bulkGet([ + { id: 'one', type: 'config' }, + { id: 'two', type: 'invalidtype' }, + { id: 'three', type: 'config' }, + { id: 'four', type: 'invalidtype' }, + { id: 'five', type: 'config' }, + ]); + + expect(savedObjects).toEqual([ + { + attributes: { title: 'Test1' }, + id: 'one', + ...mockTimestampFields, + references: [], + type: 'config', + version: mockVersion, + migrationVersion: undefined, + }, + { + attributes: { title: 'Test3' }, + id: 'three', + ...mockTimestampFields, + references: [], + type: 'config', + version: mockVersion, + migrationVersion: undefined, + }, + { + attributes: { title: 'Test5' }, + id: 'five', + ...mockTimestampFields, + references: [], + type: 'config', + version: mockVersion, + migrationVersion: undefined, + }, + { + error: { + error: 'Bad Request', + message: "Unsupported saved object type: 'invalidtype': Bad Request", + statusCode: 400, + }, + id: 'two', + type: 'invalidtype', + }, + { + error: { + error: 'Bad Request', + message: "Unsupported saved object type: 'invalidtype': Bad Request", + statusCode: 400, + }, + id: 'four', + type: 'invalidtype', + }, + ]); + }); }); describe('#update', () => { @@ -2030,59 +2114,6 @@ describe('SavedObjectsRepository', () => { ).rejects.toEqual(new Error("Unsupported saved object type: 'hiddenType': Bad Request")); }); - it("should return an error object when attempting to 'bulkGet' an unsupported type", async () => { - callAdminCluster.mockReturnValue({ - docs: [ - { - id: 'one', - type: 'config', - _primary_term: 1, - _seq_no: 1, - found: true, - _source: { - updated_at: mockTimestamp, - }, - }, - { - id: 'bad', - type: 'config', - found: false, - }, - ], - }); - const { saved_objects: savedObjects } = await savedObjectsRepository.bulkGet([ - { id: 'one', type: 'config' }, - { id: 'bad', type: 'config' }, - { id: 'four', type: 'hiddenType' }, - ]); - expect(savedObjects).toEqual([ - { - id: 'one', - type: 'config', - updated_at: mockTimestamp, - references: [], - version: 'WzEsMV0=', - }, - { - error: { - message: 'Not found', - statusCode: 404, - }, - id: 'bad', - type: 'config', - }, - { - id: 'four', - error: { - error: 'Bad Request', - message: "Unsupported saved object type: 'hiddenType': Bad Request", - statusCode: 400, - }, - type: 'hiddenType', - }, - ]); - }); - it("should not return hidden saved ojects when attempting to 'find' support and unsupported types", async () => { callAdminCluster.mockReturnValue({ hits: { diff --git a/src/legacy/server/saved_objects/service/lib/repository.js b/src/legacy/server/saved_objects/service/lib/repository.ts similarity index 68% rename from src/legacy/server/saved_objects/service/lib/repository.js rename to src/legacy/server/saved_objects/service/lib/repository.ts index c5cfce72b6702..ef4b17f5106c9 100644 --- a/src/legacy/server/saved_objects/service/lib/repository.js +++ b/src/legacy/server/saved_objects/service/lib/repository.ts @@ -17,19 +17,77 @@ * under the License. */ -import { omit, flatten } from 'lodash'; -import { getRootPropertiesObjects } from '../../../mappings'; +import { omit } from 'lodash'; +import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; +import { getRootPropertiesObjects, IndexMapping } from '../../../mappings'; import { getSearchDsl } from './search_dsl'; import { includedFields } from './included_fields'; import { decorateEsError } from './decorate_es_error'; import * as errors from './errors'; import { decodeRequestVersion, encodeVersion, encodeHitVersion } from '../../version'; +import { SavedObjectsSchema } from '../../schema'; +import { KibanaMigrator } from '../../migrations'; +import { SavedObjectsSerializer, SanitizedSavedObjectDoc, RawDoc } from '../../serialization'; +import { + BulkCreateObject, + CreateOptions, + SavedObject, + FindOptions, + SavedObjectAttributes, + FindResponse, + BulkGetObject, + BulkResponse, + UpdateOptions, + BaseOptions, + MigrationVersion, + UpdateResponse, +} from '../saved_objects_client'; // BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository // so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient. +// eslint-disable-next-line @typescript-eslint/prefer-interface +type Left = { + tag: 'Left'; + error: T; +}; +// eslint-disable-next-line @typescript-eslint/prefer-interface +type Right = { + tag: 'Right'; + value: T; +}; + +type Either = Left | Right; +const isLeft = (either: Either): either is Left => { + return either.tag === 'Left'; +}; + +export interface SavedObjectsRepositoryOptions { + index: string; + mappings: IndexMapping; + callCluster: CallCluster; + schema: SavedObjectsSchema; + serializer: SavedObjectsSerializer; + migrator: KibanaMigrator; + allowedTypes: string[]; + onBeforeWrite?: (...args: Parameters) => Promise; +} + +export interface IncrementCounterOptions extends BaseOptions { + migrationVersion?: MigrationVersion; +} + export class SavedObjectsRepository { - constructor(options) { + private _migrator: KibanaMigrator; + private _index: string; + private _mappings: IndexMapping; + private _schema: SavedObjectsSchema; + private _allowedTypes: string[]; + private _onBeforeWrite: (...args: Parameters) => Promise; + private _unwrappedCallCluster: CallCluster; + private _serializer: SavedObjectsSerializer; + + constructor(options: SavedObjectsRepositoryOptions) { const { index, mappings, @@ -38,8 +96,7 @@ export class SavedObjectsRepository { serializer, migrator, allowedTypes = [], - onBeforeWrite = () => {}, - onBeforeRead = () => {}, + onBeforeWrite = () => Promise.resolve(), } = options; // It's important that we migrate documents / mark them as up-to-date @@ -59,9 +116,8 @@ export class SavedObjectsRepository { this._allowedTypes = allowedTypes; this._onBeforeWrite = onBeforeWrite; - this._onBeforeRead = onBeforeRead; - this._unwrappedCallCluster = async (...args) => { + this._unwrappedCallCluster = async (...args: Parameters) => { await migrator.awaitMigration(); return callCluster(...args); }; @@ -82,10 +138,14 @@ export class SavedObjectsRepository { * @property {array} [options.references] - [{ name, type, id }] * @returns {promise} - { id, type, version, attributes } */ - async create(type, attributes = {}, options = {}) { - const { id, migrationVersion, overwrite = false, namespace, references = [] } = options; - - if (!this._isTypeAllowed(type)) { + public async create( + type: string, + attributes: T, + options: CreateOptions = { overwrite: false, references: [] } + ): Promise> { + const { id, migrationVersion, overwrite, namespace, references } = options; + + if (!this._allowedTypes.includes(type)) { throw errors.createUnsupportedTypeError(type); } @@ -103,11 +163,11 @@ export class SavedObjectsRepository { references, }); - const raw = this._serializer.savedObjectToRaw(migrated); + const raw = this._serializer.savedObjectToRaw(migrated as SanitizedSavedObjectDoc); const response = await this._writeToCluster(method, { id: raw._id, - index: this._getIndexForType(type), + index: this.getIndexForType(type), refresh: 'wait_for', body: raw._source, }); @@ -135,16 +195,20 @@ export class SavedObjectsRepository { * @property {string} [options.namespace] * @returns {promise} - {saved_objects: [[{ id, type, version, references, attributes, error: { message } }]} */ - async bulkCreate(objects, options = {}) { + async bulkCreate( + objects: Array>, + options: CreateOptions = {} + ): Promise> { const { namespace, overwrite = false } = options; const time = this._getCurrentTime(); - const bulkCreateParams = []; + const bulkCreateParams: object[] = []; let requestIndexCounter = 0; - const expectedResults = objects.map(object => { - if (!this._isTypeAllowed(object.type)) { + const expectedResults: Array> = objects.map(object => { + if (!this._allowedTypes.includes(object.type)) { return { - response: { + tag: 'Left' as 'Left', + error: { id: object.id, type: object.type, error: errors.createUnsupportedTypeError(object.type).output.payload, @@ -156,30 +220,28 @@ export class SavedObjectsRepository { const expectedResult = { esRequestIndex: requestIndexCounter++, requestedId: object.id, - rawMigratedDoc: this._serializer.savedObjectToRaw( - this._migrator.migrateDocument({ - id: object.id, - type: object.type, - attributes: object.attributes, - migrationVersion: object.migrationVersion, - namespace, - updated_at: time, - references: object.references || [], - }) - ), + rawMigratedDoc: this._serializer.savedObjectToRaw(this._migrator.migrateDocument({ + id: object.id, + type: object.type, + attributes: object.attributes, + migrationVersion: object.migrationVersion, + namespace, + updated_at: time, + references: object.references || [], + }) as SanitizedSavedObjectDoc), }; bulkCreateParams.push( { [method]: { _id: expectedResult.rawMigratedDoc._id, - _index: this._getIndexForType(object.type), + _index: this.getIndexForType(object.type), }, }, expectedResult.rawMigratedDoc._source ); - return expectedResult; + return { tag: 'Right' as 'Right', value: expectedResult }; }); const esResponse = await this._writeToCluster('bulk', { @@ -189,18 +251,18 @@ export class SavedObjectsRepository { return { saved_objects: expectedResults.map(expectedResult => { - if (expectedResult.response) { - return expectedResult.response; + if (isLeft(expectedResult)) { + return expectedResult.error; } - const { requestedId, rawMigratedDoc, esRequestIndex } = expectedResult; + const { requestedId, rawMigratedDoc, esRequestIndex } = expectedResult.value; const response = esResponse.items[esRequestIndex]; const { error, _id: responseId, _seq_no: seqNo, _primary_term: primaryTerm, - } = Object.values(response)[0]; + } = Object.values(response)[0] as any; const { _source: { type, [type]: attributes, references = [] }, @@ -245,8 +307,8 @@ export class SavedObjectsRepository { * @property {string} [options.namespace] * @returns {promise} */ - async delete(type, id, options = {}) { - if (!this._isTypeAllowed(type)) { + async delete(type: string, id: string, options: BaseOptions = {}): Promise<{}> { + if (!this._allowedTypes.includes(type)) { throw errors.createGenericNotFoundError(); } @@ -254,7 +316,7 @@ export class SavedObjectsRepository { const response = await this._writeToCluster('delete', { id: this._serializer.generateRawId(namespace, type, id), - index: this._getIndexForType(type), + index: this.getIndexForType(type), refresh: 'wait_for', ignore: [404], }); @@ -282,7 +344,7 @@ export class SavedObjectsRepository { * @param {string} namespace * @returns {promise} - { took, timed_out, total, deleted, batches, version_conflicts, noops, retries, failures } */ - async deleteByNamespace(namespace) { + async deleteByNamespace(namespace: string): Promise { if (!namespace || typeof namespace !== 'string') { throw new TypeError(`namespace is required, and must be a string`); } @@ -291,16 +353,8 @@ export class SavedObjectsRepository { const typesToDelete = allTypes.filter(type => !this._schema.isNamespaceAgnostic(type)); - const indexes = flatten( - Object.values(this._schema).map(schema => - Object.values(schema).map(props => props.indexPattern) - ) - ) - .filter(pattern => pattern !== undefined) - .concat([this._index]); - const esOptions = { - index: indexes, + index: this.getIndicesForTypes(typesToDelete), ignore: [404], refresh: 'wait_for', body: { @@ -331,44 +385,32 @@ export class SavedObjectsRepository { * @property {object} [options.hasReference] - { type, id } * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ - async find(options = {}) { - const { - search, - defaultSearchOperator = 'OR', - searchFields, - hasReference, - page = 1, - perPage = 20, - sortField, - sortOrder, - fields, - namespace, - } = options; - let { type } = options; - + async find({ + search, + defaultSearchOperator = 'OR', + searchFields, + hasReference, + page = 1, + perPage = 20, + sortField, + sortOrder, + fields, + namespace, + type, + }: FindOptions): Promise> { if (!type) { throw new TypeError(`options.type must be a string or an array of strings`); } - if (Array.isArray(type)) { - type = type.filter(type => this._isTypeAllowed(type)); - if (type.length === 0) { - return { - page, - per_page: perPage, - total: 0, - saved_objects: [], - }; - } - } else { - if (!this._isTypeAllowed(type)) { - return { - page, - per_page: perPage, - total: 0, - saved_objects: [], - }; - } + const types = Array.isArray(type) ? type : [type]; + const allowedTypes = types.filter(t => this._allowedTypes.includes(t)); + if (allowedTypes.length === 0) { + return { + page, + per_page: perPage, + total: 0, + saved_objects: [], + }; } if (searchFields && !Array.isArray(searchFields)) { @@ -380,7 +422,7 @@ export class SavedObjectsRepository { } const esOptions = { - index: this._getIndexForType(type), + index: this.getIndicesForTypes(allowedTypes), size: perPage, from: perPage * (page - 1), _source: includedFields(type, fields), @@ -392,7 +434,7 @@ export class SavedObjectsRepository { search, defaultSearchOperator, searchFields, - type, + type: allowedTypes, sortField, sortOrder, namespace, @@ -418,7 +460,7 @@ export class SavedObjectsRepository { page, per_page: perPage, total: response.hits.total, - saved_objects: response.hits.hits.map(hit => this._rawToSavedObject(hit)), + saved_objects: response.hits.hits.map((hit: RawDoc) => this._rawToSavedObject(hit)), }; } @@ -436,46 +478,51 @@ export class SavedObjectsRepository { * { id: 'foo', type: 'index-pattern' } * ]) */ - async bulkGet(objects = [], options = {}) { + async bulkGet( + objects: BulkGetObject[] = [], + options: BaseOptions = {} + ): Promise> { const { namespace } = options; if (objects.length === 0) { return { saved_objects: [] }; } - const unsupportedTypes = []; + const unsupportedTypeObjects = objects + .filter(o => !this._allowedTypes.includes(o.type)) + .map(({ type, id }) => { + return ({ + id, + type, + error: errors.createUnsupportedTypeError(type).output.payload, + } as any) as SavedObject; + }); + + const supportedTypeObjects = objects.filter(o => this._allowedTypes.includes(o.type)); + const response = await this._callCluster('mget', { body: { - docs: objects.reduce((acc, { type, id, fields }) => { - if (this._isTypeAllowed(type)) { - acc.push({ - _id: this._serializer.generateRawId(namespace, type, id), - _index: this._getIndexForType(type), - _source: includedFields(type, fields), - }); - } else { - unsupportedTypes.push({ - id, - type, - error: errors.createUnsupportedTypeError(type).output.payload, - }); - } - return acc; - }, []), + docs: supportedTypeObjects.map(({ type, id, fields }) => { + return { + _id: this._serializer.generateRawId(namespace, type, id), + _index: this.getIndexForType(type), + _source: includedFields(type, fields), + }; + }), }, }); return { - saved_objects: response.docs + saved_objects: (response.docs as any[]) .map((doc, i) => { - const { id, type } = objects[i]; + const { id, type } = supportedTypeObjects[i]; if (!doc.found) { - return { + return ({ id, type, error: { statusCode: 404, message: 'Not found' }, - }; + } as any) as SavedObject; } const time = doc._source.updated_at; @@ -489,7 +536,7 @@ export class SavedObjectsRepository { migrationVersion: doc._source.migrationVersion, }; }) - .concat(unsupportedTypes), + .concat(unsupportedTypeObjects), }; } @@ -502,8 +549,12 @@ export class SavedObjectsRepository { * @property {string} [options.namespace] * @returns {promise} - { id, type, version, attributes } */ - async get(type, id, options = {}) { - if (!this._isTypeAllowed(type)) { + async get( + type: string, + id: string, + options: BaseOptions = {} + ): Promise> { + if (!this._allowedTypes.includes(type)) { throw errors.createGenericNotFoundError(type, id); } @@ -511,7 +562,7 @@ export class SavedObjectsRepository { const response = await this._callCluster('get', { id: this._serializer.generateRawId(namespace, type, id), - index: this._getIndexForType(type), + index: this.getIndexForType(type), ignore: [404], }); @@ -546,8 +597,13 @@ export class SavedObjectsRepository { * @property {array} [options.references] - [{ name, type, id }] * @returns {promise} */ - async update(type, id, attributes, options = {}) { - if (!this._isTypeAllowed(type)) { + async update( + type: string, + id: string, + attributes: Partial, + options: UpdateOptions = {} + ): Promise> { + if (!this._allowedTypes.includes(type)) { throw errors.createGenericNotFoundError(type, id); } @@ -556,7 +612,7 @@ export class SavedObjectsRepository { const time = this._getCurrentTime(); const response = await this._writeToCluster('update', { id: this._serializer.generateRawId(namespace, type, id), - index: this._getIndexForType(type), + index: this.getIndexForType(type), ...(version && decodeRequestVersion(version)), refresh: 'wait_for', ignore: [404], @@ -594,14 +650,19 @@ export class SavedObjectsRepository { * @property {object} [options.migrationVersion=undefined] * @returns {promise} */ - async incrementCounter(type, id, counterFieldName, options = {}) { + async incrementCounter( + type: string, + id: string, + counterFieldName: string, + options: IncrementCounterOptions = {} + ) { if (typeof type !== 'string') { throw new Error('"type" argument must be a string'); } if (typeof counterFieldName !== 'string') { throw new Error('"counterFieldName" argument must be a string'); } - if (!this._isTypeAllowed(type)) { + if (!this._allowedTypes.includes(type)) { throw errors.createUnsupportedTypeError(type); } @@ -617,11 +678,11 @@ export class SavedObjectsRepository { updated_at: time, }); - const raw = this._serializer.savedObjectToRaw(migrated); + const raw = this._serializer.savedObjectToRaw(migrated as SanitizedSavedObjectDoc); const response = await this._writeToCluster('update', { id: this._serializer.generateRawId(namespace, type, id), - index: this._getIndexForType(type), + index: this.getIndexForType(type), refresh: 'wait_for', _source: true, body: { @@ -657,42 +718,45 @@ export class SavedObjectsRepository { }; } - async _writeToCluster(method, params) { + private async _writeToCluster(...args: Parameters) { try { - await this._onBeforeWrite(method, params); - return await this._callCluster(method, params); + await this._onBeforeWrite(...args); + return await this._callCluster(...args); } catch (err) { throw decorateEsError(err); } } - async _readFromCluster(method, params) { + private async _callCluster(...args: Parameters) { try { - await this._onBeforeRead(method, params); - return await this._callCluster(method, params); + return await this._unwrappedCallCluster(...args); } catch (err) { throw decorateEsError(err); } } - async _callCluster(method, params) { - try { - return await this._unwrappedCallCluster(method, params); - } catch (err) { - throw decorateEsError(err); - } + /** + * Returns index specified by the given type or the default index + * + * @param type - the type + */ + private getIndexForType(type: string) { + return this._schema.getIndexForType(type) || this._index; } - _getIndexForType(type) { - return ( - (this._schema.definition && - this._schema.definition[type] && - this._schema.definition[type].indexPattern) || - this._index - ); + /** + * Returns an array of indices as specified in `this._schema` for each of the + * given `types`. If any of the types don't have an associated index, the + * default index `this._index` will be included. + * + * @param types The types whose indices should be retrieved + */ + private getIndicesForTypes(types: string[]) { + const unique = (array: string[]) => [...new Set(array)]; + return unique(types.map(t => this._schema.getIndexForType(t) || this._index)); } - _getCurrentTime() { + private _getCurrentTime() { return new Date().toISOString(); } @@ -700,18 +764,8 @@ export class SavedObjectsRepository { // includes the namespace, and we use this for migrating documents. However, we don't // want the namespcae to be returned from the repository, as the repository scopes each // method transparently to the specified namespace. - _rawToSavedObject(raw) { + private _rawToSavedObject(raw: RawDoc): SavedObject { const savedObject = this._serializer.rawToSavedObject(raw); return omit(savedObject, 'namespace'); } - - _isTypeAllowed(types) { - const toCheck = [].concat(types); - for (const type of toCheck) { - if (!this._allowedTypes.includes(type)) { - return false; - } - } - return true; - } } diff --git a/src/legacy/server/saved_objects/service/lib/scoped_client_provider.d.ts b/src/legacy/server/saved_objects/service/lib/scoped_client_provider.d.ts deleted file mode 100644 index 10f3cce763f9a..0000000000000 --- a/src/legacy/server/saved_objects/service/lib/scoped_client_provider.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 { SavedObjectsClient } from '..'; - -export interface SavedObjectsClientWrapperOptions { - client: SavedObjectsClient; - request: Request; -} - -export type SavedObjectsClientWrapperFactory = ( - options: SavedObjectsClientWrapperOptions -) => SavedObjectsClient; - -export interface ScopedSavedObjectsClientProvider { - // ATTENTION: these types are incomplete - - addClientWrapperFactory( - priority: number, - wrapperFactory: SavedObjectsClientWrapperFactory - ): void; - getClient(request: Request): SavedObjectsClient; -} diff --git a/src/legacy/server/saved_objects/service/lib/scoped_client_provider.js b/src/legacy/server/saved_objects/service/lib/scoped_client_provider.ts similarity index 57% rename from src/legacy/server/saved_objects/service/lib/scoped_client_provider.js rename to src/legacy/server/saved_objects/service/lib/scoped_client_provider.ts index 4049dcbf49f74..201b316005d7c 100644 --- a/src/legacy/server/saved_objects/service/lib/scoped_client_provider.js +++ b/src/legacy/server/saved_objects/service/lib/scoped_client_provider.ts @@ -17,22 +17,47 @@ * under the License. */ import { PriorityCollection } from './priority_collection'; +import { SavedObjectsClientContract } from '..'; + +export interface SavedObjectsClientWrapperOptions { + client: SavedObjectsClientContract; + request: Request; +} + +export type SavedObjectsClientWrapperFactory = ( + options: SavedObjectsClientWrapperOptions +) => SavedObjectsClientContract; + +export type SavedObjectsClientFactory = ( + { request }: { request: Request } +) => SavedObjectsClientContract; /** * Provider for the Scoped Saved Object Client. */ -export class ScopedSavedObjectsClientProvider { - _wrapperFactories = new PriorityCollection(); +export class ScopedSavedObjectsClientProvider { + private readonly _wrapperFactories = new PriorityCollection< + SavedObjectsClientWrapperFactory + >(); + private _clientFactory: SavedObjectsClientFactory; + private readonly _originalClientFactory: SavedObjectsClientFactory; - constructor({ defaultClientFactory }) { + constructor({ + defaultClientFactory, + }: { + defaultClientFactory: SavedObjectsClientFactory; + }) { this._originalClientFactory = this._clientFactory = defaultClientFactory; } - addClientWrapperFactory(priority, wrapperFactory) { + addClientWrapperFactory( + priority: number, + wrapperFactory: SavedObjectsClientWrapperFactory + ): void { this._wrapperFactories.add(priority, wrapperFactory); } - setClientFactory(customClientFactory) { + setClientFactory(customClientFactory: SavedObjectsClientFactory) { if (this._clientFactory !== this._originalClientFactory) { throw new Error(`custom client factory is already set, unable to replace the current one`); } @@ -40,7 +65,7 @@ export class ScopedSavedObjectsClientProvider { this._clientFactory = customClientFactory; } - getClient(request) { + getClient(request: Request): SavedObjectsClientContract { const client = this._clientFactory({ request, }); diff --git a/src/legacy/server/saved_objects/service/lib/search_dsl/search_dsl.ts b/src/legacy/server/saved_objects/service/lib/search_dsl/search_dsl.ts index 5baf46e2edab7..83e06eb17ccf2 100644 --- a/src/legacy/server/saved_objects/service/lib/search_dsl/search_dsl.ts +++ b/src/legacy/server/saved_objects/service/lib/search_dsl/search_dsl.ts @@ -25,7 +25,7 @@ import { getQueryParams } from './query_params'; import { getSortingParams } from './sorting_params'; interface GetSearchDslOptions { - type: string; + type: string | string[]; search?: string; defaultSearchOperator?: string; searchFields?: string[]; diff --git a/src/legacy/server/saved_objects/service/saved_objects_client.d.ts b/src/legacy/server/saved_objects/service/saved_objects_client.d.ts deleted file mode 100644 index cb9ace7bcb1ba..0000000000000 --- a/src/legacy/server/saved_objects/service/saved_objects_client.d.ts +++ /dev/null @@ -1,149 +0,0 @@ -/* - * 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 { errors, SavedObjectsRepository } from './lib'; - -export interface BaseOptions { - namespace?: string; -} - -export interface CreateOptions extends BaseOptions { - id?: string; - overwrite?: boolean; - migrationVersion?: MigrationVersion; - references?: SavedObjectReference[]; -} - -export interface BulkCreateObject { - id?: string; - type: string; - attributes: T; - extraDocumentProperties?: string[]; -} - -export interface BulkCreateResponse { - saved_objects: Array>; -} - -export interface FindOptions extends BaseOptions { - type?: string | string[]; - page?: number; - perPage?: number; - sortField?: string; - sortOrder?: string; - fields?: string[]; - search?: string; - searchFields?: string[]; - hasReference?: { type: string; id: string }; - defaultSearchOperator?: 'AND' | 'OR'; -} - -export interface FindResponse { - saved_objects: Array>; - total: number; - per_page: number; - page: number; -} - -export interface UpdateOptions extends BaseOptions { - version?: string; -} - -export interface BulkGetObject { - id: string; - type: string; - fields?: string[]; -} -export type BulkGetObjects = BulkGetObject[]; - -export interface BulkGetResponse { - saved_objects: Array>; -} - -export interface MigrationVersion { - [pluginName: string]: string; -} - -export interface SavedObjectAttributes { - [key: string]: SavedObjectAttributes | string | number | boolean | null; -} - -export interface VisualizationAttributes extends SavedObjectAttributes { - visState: string; -} - -export interface SavedObject { - id: string; - type: string; - version?: string; - updated_at?: string; - error?: { - message: string; - statusCode: number; - }; - attributes: T; - references: SavedObjectReference[]; - migrationVersion?: MigrationVersion; -} - -export interface SavedObjectReference { - name: string; - type: string; - id: string; -} - -export type GetResponse = SavedObject; -export type CreateResponse = SavedObject; -export type UpdateResponse = SavedObject; - -export declare class SavedObjectsClient { - public static errors: typeof errors; - public errors: typeof errors; - - constructor(repository: SavedObjectsRepository); - - public create( - type: string, - attributes: T, - options?: CreateOptions - ): Promise>; - public bulkCreate( - objects: Array>, - options?: CreateOptions - ): Promise>; - public delete(type: string, id: string, options?: BaseOptions): Promise<{}>; - public find( - options: FindOptions - ): Promise>; - public bulkGet( - objects: BulkGetObjects, - options?: BaseOptions - ): Promise>; - public get( - type: string, - id: string, - options?: BaseOptions - ): Promise>; - public update( - type: string, - id: string, - attributes: Partial, - options?: UpdateOptions - ): Promise>; -} diff --git a/src/legacy/server/saved_objects/service/saved_objects_client.js b/src/legacy/server/saved_objects/service/saved_objects_client.js deleted file mode 100644 index f98b99688bf23..0000000000000 --- a/src/legacy/server/saved_objects/service/saved_objects_client.js +++ /dev/null @@ -1,202 +0,0 @@ -/* - * 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 { errors } from './lib'; - -export class SavedObjectsClient { - constructor(repository) { - this._repository = repository; - } - - /** - * ## SavedObjectsClient errors - * - * Since the SavedObjectsClient has its hands in everything we - * are a little paranoid about the way we present errors back to - * to application code. Ideally, all errors will be either: - * - * 1. Caused by bad implementation (ie. undefined is not a function) and - * as such unpredictable - * 2. An error that has been classified and decorated appropriately - * by the decorators in `./lib/errors` - * - * Type 1 errors are inevitable, but since all expected/handle-able errors - * should be Type 2 the `isXYZError()` helpers exposed at - * `savedObjectsClient.errors` should be used to understand and manage error - * responses from the `SavedObjectsClient`. - * - * Type 2 errors are decorated versions of the source error, so if - * the elasticsearch client threw an error it will be decorated based - * on its type. That means that rather than looking for `error.body.error.type` or - * doing substring checks on `error.body.error.reason`, just use the helpers to - * understand the meaning of the error: - * - * ```js - * if (savedObjectsClient.errors.isNotFoundError(error)) { - * // handle 404 - * } - * - * if (savedObjectsClient.errors.isNotAuthorizedError(error)) { - * // 401 handling should be automatic, but in case you wanted to know - * } - * - * // always rethrow the error unless you handle it - * throw error; - * ``` - * - * ### 404s from missing index - * - * From the perspective of application code and APIs the SavedObjectsClient is - * a black box that persists objects. One of the internal details that users have - * no control over is that we use an elasticsearch index for persistance and that - * index might be missing. - * - * At the time of writing we are in the process of transitioning away from the - * operating assumption that the SavedObjects index is always available. Part of - * this transition is handling errors resulting from an index missing. These used - * to trigger a 500 error in most cases, and in others cause 404s with different - * error messages. - * - * From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The - * object the request/call was targeting could not be found. This is why #14141 - * takes special care to ensure that 404 errors are generic and don't distinguish - * between index missing or document missing. - * - * ### 503s from missing index - * - * Unlike all other methods, create requests are supposed to succeed even when - * the Kibana index does not exist because it will be automatically created by - * elasticsearch. When that is not the case it is because Elasticsearch's - * `action.auto_create_index` setting prevents it from being created automatically - * so we throw a special 503 with the intention of informing the user that their - * Elasticsearch settings need to be updated. - * - * @type {ErrorHelpers} see ./lib/errors - */ - static errors = errors; - errors = errors; - - /** - * Persists an object - * - * @param {string} type - * @param {object} attributes - * @param {object} [options={}] - * @property {string} [options.id] - force id on creation, not recommended - * @property {boolean} [options.overwrite=false] - * @property {object} [options.migrationVersion=undefined] - * @property {string} [options.namespace] - * @property {array} [options.references] - [{ name, type, id }] - * @returns {promise} - { id, type, version, attributes } - */ - async create(type, attributes = {}, options = {}) { - return this._repository.create(type, attributes, options); - } - - /** - * Creates multiple documents at once - * - * @param {array} objects - [{ type, id, attributes }] - * @param {object} [options={}] - * @property {boolean} [options.overwrite=false] - overwrites existing documents - * @property {string} [options.namespace] - * @returns {promise} - { saved_objects: [{ id, type, version, attributes, error: { message } }]} - */ - async bulkCreate(objects, options = {}) { - return this._repository.bulkCreate(objects, options); - } - - /** - * Deletes an object - * - * @param {string} type - * @param {string} id - * @param {object} [options={}] - * @property {string} [options.namespace] - * @returns {promise} - */ - async delete(type, id, options = {}) { - return this._repository.delete(type, id, options); - } - - /** - * @param {object} [options={}] - * @property {(string|Array)} [options.type] - * @property {string} [options.search] - * @property {string} [options.defaultSearchOperator] - * @property {Array} [options.searchFields] - see Elasticsearch Simple Query String - * Query field argument for more information - * @property {integer} [options.page=1] - * @property {integer} [options.perPage=20] - * @property {string} [options.sortField] - * @property {string} [options.sortOrder] - * @property {Array} [options.fields] - * @property {string} [options.namespace] - * @property {object} [options.hasReference] - { type, id } - * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } - */ - async find(options = {}) { - return this._repository.find(options); - } - - /** - * Returns an array of objects by id - * - * @param {array} objects - an array ids, or an array of objects containing id and optionally type - * @param {object} [options={}] - * @property {string} [options.namespace] - * @returns {promise} - { saved_objects: [{ id, type, version, attributes }] } - * @example - * - * bulkGet([ - * { id: 'one', type: 'config' }, - * { id: 'foo', type: 'index-pattern' } - * ]) - */ - async bulkGet(objects = [], options = {}) { - return this._repository.bulkGet(objects, options); - } - - /** - * Gets a single object - * - * @param {string} type - * @param {string} id - * @param {object} [options={}] - * @property {string} [options.namespace] - * @returns {promise} - { id, type, version, attributes } - */ - async get(type, id, options = {}) { - return this._repository.get(type, id, options); - } - - /** - * Updates an object - * - * @param {string} type - * @param {string} id - * @param {object} [options={}] - * @property {integer} options.version - ensures version matches that of persisted object - * @property {string} [options.namespace] - * @returns {promise} - */ - async update(type, id, attributes, options = {}) { - return this._repository.update(type, id, attributes, options); - } -} diff --git a/src/legacy/server/saved_objects/index.d.ts b/src/legacy/server/saved_objects/service/saved_objects_client.mock.ts similarity index 67% rename from src/legacy/server/saved_objects/index.d.ts rename to src/legacy/server/saved_objects/service/saved_objects_client.mock.ts index e49dc27b9598a..16de6e4eb5b52 100644 --- a/src/legacy/server/saved_objects/index.d.ts +++ b/src/legacy/server/saved_objects/service/saved_objects_client.mock.ts @@ -17,16 +17,18 @@ * under the License. */ -export { - MigrationVersion, - SavedObject, - SavedObjectAttributes, - SavedObjectsClient, - SavedObjectsClientWrapperFactory, - SavedObjectReference, - SavedObjectsService, -} from './service'; +import { SavedObjectsClientContract } from './saved_objects_client'; +import * as errors from './lib/errors'; -export { SavedObjectsSchema } from './schema'; +const create = (): jest.Mocked => ({ + errors, + create: jest.fn(), + bulkCreate: jest.fn(), + delete: jest.fn(), + bulkGet: jest.fn(), + find: jest.fn(), + get: jest.fn(), + update: jest.fn(), +}); -export { SavedObjectsManagement } from './management'; +export const SavedObjectsClientMock = { create }; diff --git a/src/legacy/server/saved_objects/service/saved_objects_client.ts b/src/legacy/server/saved_objects/service/saved_objects_client.ts new file mode 100644 index 0000000000000..a0d378bfc5a97 --- /dev/null +++ b/src/legacy/server/saved_objects/service/saved_objects_client.ts @@ -0,0 +1,307 @@ +/* + * 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 { errors, SavedObjectsRepository } from './lib'; + +type Omit = Pick>; + +export interface BaseOptions { + /** Specify the namespace for this operation */ + namespace?: string; +} + +export interface CreateOptions extends BaseOptions { + /** (not recommended) Specify an id for the document */ + id?: string; + /** Overwrite existing documents (defaults to false) */ + overwrite?: boolean; + migrationVersion?: MigrationVersion; + references?: SavedObjectReference[]; +} + +export interface BulkCreateObject { + id?: string; + type: string; + attributes: T; + references?: SavedObjectReference[]; + migrationVersion?: MigrationVersion; +} + +export interface BulkResponse { + saved_objects: Array>; +} + +export interface FindOptions extends BaseOptions { + type?: string | string[]; + page?: number; + perPage?: number; + sortField?: string; + sortOrder?: string; + fields?: string[]; + search?: string; + /** see Elasticsearch Simple Query String Query field argument for more information */ + searchFields?: string[]; + hasReference?: { type: string; id: string }; + defaultSearchOperator?: 'AND' | 'OR'; +} + +export interface FindResponse { + saved_objects: Array>; + total: number; + per_page: number; + page: number; +} + +export interface UpdateOptions extends BaseOptions { + /** Ensures version matches that of persisted object */ + version?: string; + references?: SavedObjectReference[]; +} + +export interface BulkGetObject { + id: string; + type: string; + /** SavedObject fields to include in the response */ + fields?: string[]; +} + +export interface BulkResponse { + saved_objects: Array>; +} + +export interface UpdateResponse + extends Omit, 'attributes'> { + attributes: Partial; +} + +/** + * A dictionary of saved object type -> version used to determine + * what migrations need to be applied to a saved object. + */ +export interface MigrationVersion { + [pluginName: string]: string; +} + +export interface SavedObjectAttributes { + [key: string]: SavedObjectAttributes | string | number | boolean | null; +} + +export interface VisualizationAttributes extends SavedObjectAttributes { + visState: string; +} + +export interface SavedObject { + id: string; + type: string; + version?: string; + updated_at?: string; + error?: { + message: string; + statusCode: number; + }; + attributes: T; + references: SavedObjectReference[]; + migrationVersion?: MigrationVersion; +} + +/** + * A reference to another saved object. + */ +export interface SavedObjectReference { + name: string; + type: string; + id: string; +} + +export type SavedObjectsClientContract = Pick; + +export class SavedObjectsClient { + /** + * ## SavedObjectsClient errors + * + * Since the SavedObjectsClient has its hands in everything we + * are a little paranoid about the way we present errors back to + * to application code. Ideally, all errors will be either: + * + * 1. Caused by bad implementation (ie. undefined is not a function) and + * as such unpredictable + * 2. An error that has been classified and decorated appropriately + * by the decorators in `./lib/errors` + * + * Type 1 errors are inevitable, but since all expected/handle-able errors + * should be Type 2 the `isXYZError()` helpers exposed at + * `savedObjectsClient.errors` should be used to understand and manage error + * responses from the `SavedObjectsClient`. + * + * Type 2 errors are decorated versions of the source error, so if + * the elasticsearch client threw an error it will be decorated based + * on its type. That means that rather than looking for `error.body.error.type` or + * doing substring checks on `error.body.error.reason`, just use the helpers to + * understand the meaning of the error: + * + * ```js + * if (savedObjectsClient.errors.isNotFoundError(error)) { + * // handle 404 + * } + * + * if (savedObjectsClient.errors.isNotAuthorizedError(error)) { + * // 401 handling should be automatic, but in case you wanted to know + * } + * + * // always rethrow the error unless you handle it + * throw error; + * ``` + * + * ### 404s from missing index + * + * From the perspective of application code and APIs the SavedObjectsClient is + * a black box that persists objects. One of the internal details that users have + * no control over is that we use an elasticsearch index for persistance and that + * index might be missing. + * + * At the time of writing we are in the process of transitioning away from the + * operating assumption that the SavedObjects index is always available. Part of + * this transition is handling errors resulting from an index missing. These used + * to trigger a 500 error in most cases, and in others cause 404s with different + * error messages. + * + * From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The + * object the request/call was targeting could not be found. This is why #14141 + * takes special care to ensure that 404 errors are generic and don't distinguish + * between index missing or document missing. + * + * ### 503s from missing index + * + * Unlike all other methods, create requests are supposed to succeed even when + * the Kibana index does not exist because it will be automatically created by + * elasticsearch. When that is not the case it is because Elasticsearch's + * `action.auto_create_index` setting prevents it from being created automatically + * so we throw a special 503 with the intention of informing the user that their + * Elasticsearch settings need to be updated. + * + * @type {ErrorHelpers} see ./lib/errors + */ + public static errors = errors; + public errors = errors; + + private _repository: SavedObjectsRepository; + + constructor(repository: SavedObjectsRepository) { + this._repository = repository; + } + + /** + * Persists a SavedObject + * + * @param type + * @param attributes + * @param options + */ + async create( + type: string, + attributes: T, + options?: CreateOptions + ) { + return await this._repository.create(type, attributes, options); + } + + /** + * Persists multiple documents batched together as a single request + * + * @param objects + * @param options + */ + async bulkCreate( + objects: Array>, + options?: CreateOptions + ) { + return await this._repository.bulkCreate(objects, options); + } + + /** + * Deletes a SavedObject + * + * @param type + * @param id + * @param options + */ + async delete(type: string, id: string, options: BaseOptions = {}) { + return await this._repository.delete(type, id, options); + } + + /** + * Find all SavedObjects matching the search query + * + * @param options + */ + async find( + options: FindOptions + ): Promise> { + return await this._repository.find(options); + } + + /** + * Returns an array of objects by id + * + * @param objects - an array of ids, or an array of objects containing id, type and optionally fields + * @example + * + * bulkGet([ + * { id: 'one', type: 'config' }, + * { id: 'foo', type: 'index-pattern' } + * ]) + */ + async bulkGet( + objects: BulkGetObject[] = [], + options: BaseOptions = {} + ): Promise> { + return await this._repository.bulkGet(objects, options); + } + + /** + * Retrieves a single object + * + * @param type - The type of SavedObject to retrieve + * @param id - The ID of the SavedObject to retrieve + * @param options + */ + async get( + type: string, + id: string, + options: BaseOptions = {} + ): Promise> { + return await this._repository.get(type, id, options); + } + + /** + * Updates an SavedObject + * + * @param type + * @param id + * @param options + */ + async update( + type: string, + id: string, + attributes: Partial, + options: UpdateOptions = {} + ): Promise> { + return await this._repository.update(type, id, attributes, options); + } +} diff --git a/src/legacy/ui/public/error_auto_create_index/error_auto_create_index.test.js b/src/legacy/ui/public/error_auto_create_index/error_auto_create_index.test.js index 645937f050f90..a8f6318090b1d 100644 --- a/src/legacy/ui/public/error_auto_create_index/error_auto_create_index.test.js +++ b/src/legacy/ui/public/error_auto_create_index/error_auto_create_index.test.js @@ -73,7 +73,9 @@ describe('isAutoCreateIndexError correctly handles KFetchError thrown by kfetch' matcher: '*', response: { body: { - code: 'ES_AUTO_CREATE_INDEX_ERROR', + attributes: { + code: 'ES_AUTO_CREATE_INDEX_ERROR', + }, }, status: 503, }, diff --git a/src/legacy/ui/public/error_auto_create_index/error_auto_create_index.ts b/src/legacy/ui/public/error_auto_create_index/error_auto_create_index.ts index e5287f18e1f7a..09c6bfd93148f 100644 --- a/src/legacy/ui/public/error_auto_create_index/error_auto_create_index.ts +++ b/src/legacy/ui/public/error_auto_create_index/error_auto_create_index.ts @@ -37,7 +37,8 @@ uiRoutes.when('/error/action.auto_create_index', { export function isAutoCreateIndexError(error: object) { return ( - get(error, 'res.status') === 503 && get(error, 'body.code') === 'ES_AUTO_CREATE_INDEX_ERROR' + get(error, 'res.status') === 503 && + get(error, 'body.attributes.code') === 'ES_AUTO_CREATE_INDEX_ERROR' ); } diff --git a/src/legacy/ui/public/saved_objects/saved_objects_client.ts b/src/legacy/ui/public/saved_objects/saved_objects_client.ts index fe94afc7f14f2..6fee8826d1638 100644 --- a/src/legacy/ui/public/saved_objects/saved_objects_client.ts +++ b/src/legacy/ui/public/saved_objects/saved_objects_client.ts @@ -22,12 +22,12 @@ import { resolve as resolveUrl } from 'url'; import { MigrationVersion, - SavedObject as PlainSavedObject, + SavedObject, SavedObjectAttributes, SavedObjectReference, SavedObjectsClient as SavedObjectsApi, } from '../../../server/saved_objects'; -import { CreateResponse, FindOptions, UpdateResponse } from '../../../server/saved_objects/service'; +import { FindOptions } from '../../../server/saved_objects/service'; import { isAutoCreateIndexError, showAutoCreateIndexErrorPage } from '../error_auto_create_index'; import { kfetch, KFetchQuery } from '../kfetch'; import { keysToCamelCaseShallow, keysToSnakeCaseShallow } from '../utils/case_conversion'; @@ -73,9 +73,7 @@ interface FindResults interface BatchQueueEntry { type: string; id: string; - resolve: ( - value: SimpleSavedObject | PlainSavedObject - ) => void; + resolve: (value: SimpleSavedObject | SavedObject) => void; reject: (reason?: any) => void; } @@ -165,7 +163,7 @@ export class SavedObjectsClient { overwrite: options.overwrite, }; - const createRequest: Promise> = this.request({ + const createRequest: Promise> = this.request({ method: 'POST', path, query, @@ -334,18 +332,17 @@ export class SavedObjectsClient { version, }; - const request: Promise> = this.request({ + return this.request({ method: 'PUT', path, body, - }); - return request.then(resp => { + }).then((resp: SavedObject) => { return this.createSavedObject(resp); }); } private createSavedObject( - options: PlainSavedObject + options: SavedObject ): SimpleSavedObject { return new SimpleSavedObject(this, options); } diff --git a/src/legacy/ui/public/saved_objects/simple_saved_object.ts b/src/legacy/ui/public/saved_objects/simple_saved_object.ts index 4bb20efdf43fa..d742b103afdd0 100644 --- a/src/legacy/ui/public/saved_objects/simple_saved_object.ts +++ b/src/legacy/ui/public/saved_objects/simple_saved_object.ts @@ -70,7 +70,7 @@ export class SimpleSavedObject { return has(this.attributes, key); } - public save() { + public save(): Promise> { if (this.id) { return this.client.update(this.type, this.id, this.attributes, { migrationVersion: this.migrationVersion, diff --git a/x-pack/package.json b/x-pack/package.json index 9334b1f757746..cd4ec5592fbc2 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -52,7 +52,7 @@ "@types/d3-shape": "^1.3.1", "@types/d3-time": "^1.0.7", "@types/d3-time-format": "^2.1.0", - "@types/elasticsearch": "^5.0.30", + "@types/elasticsearch": "^5.0.33", "@types/file-saver": "^2.0.0", "@types/git-url-parse": "^9.0.0", "@types/glob": "^7.1.1", @@ -260,12 +260,12 @@ "mapbox-gl": "0.54.0", "mapbox-gl-draw-rectangle-mode": "^1.0.4", "markdown-it": "^8.4.1", + "memoize-one": "^5.0.0", "mime": "^2.2.2", "mkdirp": "0.5.1", "moment": "^2.20.1", "moment-duration-format": "^1.3.0", "moment-timezone": "^0.5.14", - "memoize-one": "^5.0.0", "monaco-editor": "^0.17.0", "ngreact": "^0.5.1", "nock": "10.0.4", diff --git a/x-pack/plugins/encrypted_saved_objects/server/lib/encrypted_saved_objects_client_wrapper.test.ts b/x-pack/plugins/encrypted_saved_objects/server/lib/encrypted_saved_objects_client_wrapper.test.ts index adb41a2eb2094..2ea2e55eee8f5 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/lib/encrypted_saved_objects_client_wrapper.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/lib/encrypted_saved_objects_client_wrapper.test.ts @@ -9,26 +9,14 @@ jest.mock('uuid', () => ({ v4: jest.fn().mockReturnValue('uuid-v4-id') })); import { EncryptedSavedObjectsClientWrapper } from './encrypted_saved_objects_client_wrapper'; import { EncryptedSavedObjectsService } from './encrypted_saved_objects_service'; import { createEncryptedSavedObjectsServiceMock } from './encrypted_saved_objects_service.mock'; -import { SavedObjectsClient } from 'src/legacy/server/saved_objects/service/saved_objects_client'; - -function createSavedObjectsClientMock(): jest.Mocked { - return { - errors: {} as any, - bulkCreate: jest.fn(), - bulkGet: jest.fn(), - create: jest.fn(), - delete: jest.fn(), - find: jest.fn(), - get: jest.fn(), - update: jest.fn(), - }; -} +import { SavedObjectsClientMock } from '../../../../../src/legacy/server/saved_objects/service/saved_objects_client.mock'; +import { SavedObjectsClientContract } from 'src/legacy/server/saved_objects'; let wrapper: EncryptedSavedObjectsClientWrapper; -let mockBaseClient: jest.Mocked; +let mockBaseClient: jest.Mocked; let encryptedSavedObjectsServiceMock: jest.Mocked; beforeEach(() => { - mockBaseClient = createSavedObjectsClientMock(); + mockBaseClient = SavedObjectsClientMock.create(); encryptedSavedObjectsServiceMock = createEncryptedSavedObjectsServiceMock([ { type: 'known-type', diff --git a/x-pack/plugins/encrypted_saved_objects/server/lib/encrypted_saved_objects_client_wrapper.ts b/x-pack/plugins/encrypted_saved_objects/server/lib/encrypted_saved_objects_client_wrapper.ts index 6afaa9b8f769f..35fa46f4e3739 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/lib/encrypted_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/lib/encrypted_saved_objects_client_wrapper.ts @@ -8,23 +8,21 @@ import uuid from 'uuid'; import { BaseOptions, BulkCreateObject, - BulkCreateResponse, - BulkGetObjects, - BulkGetResponse, + BulkGetObject, + BulkResponse, CreateOptions, - CreateResponse, FindOptions, FindResponse, - GetResponse, SavedObjectAttributes, - SavedObjectsClient, + SavedObjectsClientContract, UpdateOptions, UpdateResponse, -} from 'src/legacy/server/saved_objects/service/saved_objects_client'; + SavedObject, +} from 'src/legacy/server/saved_objects'; import { EncryptedSavedObjectsService } from './encrypted_saved_objects_service'; interface EncryptedSavedObjectsClientOptions { - baseClient: SavedObjectsClient; + baseClient: SavedObjectsClientContract; service: Readonly; } @@ -36,10 +34,10 @@ function generateID() { return uuid.v4(); } -export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClient { +export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientContract { constructor( private readonly options: EncryptedSavedObjectsClientOptions, - public readonly errors: SavedObjectsClient['errors'] = options.baseClient.errors + public readonly errors = options.baseClient.errors ) {} public async create( @@ -119,7 +117,7 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClient { ); } - public async bulkGet(objects: BulkGetObjects = [], options?: BaseOptions) { + public async bulkGet(objects: BulkGetObject[] = [], options?: BaseOptions) { return this.stripEncryptedAttributesFromBulkResponse( await this.options.baseClient.bulkGet(objects, options) ); @@ -159,9 +157,9 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClient { * registered, response is returned as is. * @param response Raw response returned by the underlying base client. */ - private stripEncryptedAttributesFromResponse< - T extends UpdateResponse | CreateResponse | GetResponse - >(response: T): T { + private stripEncryptedAttributesFromResponse( + response: T + ): T { if (this.options.service.isRegistered(response.type)) { response.attributes = this.options.service.stripEncryptedAttributes( response.type, @@ -177,9 +175,9 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClient { * response portion isn't registered, it is returned as is. * @param response Raw response returned by the underlying base client. */ - private stripEncryptedAttributesFromBulkResponse< - T extends BulkCreateResponse | BulkGetResponse | FindResponse - >(response: T): T { + private stripEncryptedAttributesFromBulkResponse( + response: T + ): T { for (const savedObject of response.saved_objects) { if (this.options.service.isRegistered(savedObject.type)) { savedObject.attributes = this.options.service.stripEncryptedAttributes( diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.ts b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.ts index b7326e1f344a4..c1873e52a2b8e 100644 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.ts +++ b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.ts @@ -7,18 +7,18 @@ import { BaseOptions, BulkCreateObject, - BulkGetObjects, + BulkGetObject, CreateOptions, FindOptions, SavedObjectAttributes, - SavedObjectsClient, + SavedObjectsClientContract, UpdateOptions, -} from 'src/legacy/server/saved_objects/service/saved_objects_client'; +} from 'src/legacy/server/saved_objects'; import { DEFAULT_SPACE_ID } from '../../../common/constants'; import { SpacesService } from '../create_spaces_service'; interface SpacesSavedObjectsClientOptions { - baseClient: SavedObjectsClient; + baseClient: SavedObjectsClientContract; request: any; spacesService: SpacesService; types: string[]; @@ -58,19 +58,19 @@ const throwErrorIfTypesContainsSpace = (types: string[]) => { } }; -export class SpacesSavedObjectsClient implements SavedObjectsClient { - public readonly errors: any; - private readonly client: SavedObjectsClient; +export class SpacesSavedObjectsClient implements SavedObjectsClientContract { + private readonly client: SavedObjectsClientContract; private readonly spaceId: string; private readonly types: string[]; + public readonly errors: SavedObjectsClientContract['errors']; constructor(options: SpacesSavedObjectsClientOptions) { const { baseClient, request, spacesService, types } = options; - this.errors = baseClient.errors; this.client = baseClient; this.spaceId = spacesService.getSpaceId(request); this.types = types; + this.errors = baseClient.errors; } /** @@ -101,7 +101,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClient { /** * Creates multiple documents at once * - * @param {array} objects - [{ type, id, attributes, extraDocumentProperties }] + * @param {array} objects - [{ type, id, attributes }] * @param {object} [options={}] * @property {boolean} [options.overwrite=false] - overwrites existing documents * @property {string} [options.namespace] @@ -182,7 +182,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClient { * { id: 'foo', type: 'index-pattern' } * ]) */ - public async bulkGet(objects: BulkGetObjects = [], options: BaseOptions = {}) { + public async bulkGet(objects: BulkGetObject[] = [], options: BaseOptions = {}) { throwErrorIfTypesContainsSpace(objects.map(object => object.type)); throwErrorIfNamespaceSpecified(options); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts index ba50e659f1e20..70e33bc406ac4 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts @@ -146,7 +146,7 @@ export const reindexActionsFactory = ( reindexOp.id, { ...reindexOp.attributes, locked: moment().format() }, { version: reindexOp.version } - ); + ) as Promise; }; const releaseLock = (reindexOp: ReindexSavedObject) => { @@ -155,7 +155,7 @@ export const reindexActionsFactory = ( reindexOp.id, { ...reindexOp.attributes, locked: null }, { version: reindexOp.version } - ); + ) as Promise; }; // ----- Public interface @@ -186,7 +186,7 @@ export const reindexActionsFactory = ( const newAttrs = { ...reindexOp.attributes, locked: moment().format(), ...attrs }; return client.update(REINDEX_OP_TYPE, reindexOp.id, newAttrs, { version: reindexOp.version, - }); + }) as Promise; }, async runWhileLocked(reindexOp, func) { diff --git a/x-pack/test/typings/index.d.ts b/x-pack/test/typings/index.d.ts index 0688ef9e4d8ec..b365d09392edd 100644 --- a/x-pack/test/typings/index.d.ts +++ b/x-pack/test/typings/index.d.ts @@ -9,3 +9,8 @@ declare module '*.html' { // eslint-disable-next-line import/no-default-export export default template; } + +declare module 'lodash/internal/toPath' { + function toPath(value: string | string[]): string[]; + export = toPath; +} diff --git a/x-pack/typings/index.d.ts b/x-pack/typings/index.d.ts index 0688ef9e4d8ec..69e7bf130210d 100644 --- a/x-pack/typings/index.d.ts +++ b/x-pack/typings/index.d.ts @@ -9,3 +9,14 @@ declare module '*.html' { // eslint-disable-next-line import/no-default-export export default template; } + +declare module 'lodash/internal/toPath' { + function toPath(value: string | string[]): string[]; + export = toPath; +} + +type MethodKeysOf = { + [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never +}[keyof T]; + +type PublicMethodsOf = Pick>; diff --git a/yarn.lock b/yarn.lock index 63fedde0f65d4..f1f4e718e0540 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3351,10 +3351,10 @@ resolved "https://registry.yarnpkg.com/@types/delete-empty/-/delete-empty-2.0.0.tgz#1647ae9e68f708a6ba778531af667ec55bc61964" integrity sha512-sq+kwx8zA9BSugT9N+Jr8/uWjbHMZ+N/meJEzRyT3gmLq/WMtx/iSIpvdpmBUi/cvXl6Kzpvve8G2ESkabFwmg== -"@types/elasticsearch@^5.0.30": - version "5.0.30" - resolved "https://registry.yarnpkg.com/@types/elasticsearch/-/elasticsearch-5.0.30.tgz#3c52f7119e3a20a47e2feb8e2b4cc54030a54e23" - integrity sha512-swxiNcLOtnHhJhAE5HcUL3WsKLHr8rEQ+fwpaJ0x4dfEE3oK2kGUoyz4wCcQfvulcMm2lShyxZ+2E4BQJzsAlg== +"@types/elasticsearch@^5.0.33": + version "5.0.33" + resolved "https://registry.yarnpkg.com/@types/elasticsearch/-/elasticsearch-5.0.33.tgz#b0fd37dc674f498223b6d68c313bdfd71f4d812b" + integrity sha512-n/g9pqJEpE4fyUE8VvHNGtl7E2Wv8TCroNwfgAeJKRV4ghDENahtrAo1KMsFNIejBD2gDAlEUa4CM4oEEd8p9Q== "@types/enzyme@^3.1.12": version "3.1.18"