From 72ef14c393565624951a0e195fb24bc1e62e99c8 Mon Sep 17 00:00:00 2001 From: Rudolf Meijering Date: Thu, 6 Jun 2019 10:49:13 +0200 Subject: [PATCH] [Core] Rewrite saved objects in typescript (#36829) * Convert simple files to TS * Fix jest tests * Rename saved_objects_client{.js => .ts} * WIP saved_objects_client * saved_objects repository{.js => .ts} * includedFields support string[] for type paramater * Repository/saved_objects_client -> TS * Fix tests and dependencies * Fix saved objects type errors and simplify * saved_objects/index saved_objects/service/index -> ts * Fix saved objects export test after switching to typed mock * Workaround type error * Revert "Workaround type error" This reverts commit de3252267eb2e6bf56a5584d271b55a7afdc1c53. * Correctly type Server.savedObjects.SaveObjectsClient constructor * saved_objects/service/lib/index.{js -> ts} * saved_objects/service/lib/scoped_client_provider{js -> ts} * Typescriptify scoped_client_provider * Fix x-pack jest imports * Add lodash/internal/toPath typings to xpath * Introduce SavedObjectsClientContract We need a way to specify that injected dependencies should adhere to the SavedObjectsClient "contract". We can't use the SavedObjectsClient class itself since it contains the private _repository property which in TS is included in the type signature of a class. * Cleanup and simplify types * Fix repository#delete should return {} * Add SavedObjects repository test for uncovered bug Test for a bug in our previous js implementation that can lead to data corruption and data loss. If a bulkGet request is made where one of the objects to fetch is of a type that isn't allowed, the returned result will include documents which have the incorrect id and type assigned. E.g. the data of an object with id '1' is returned with id '2'. Saving '2' will incorrectly override it's data with that of the data of object '1'. * SavedObject.updated_at: string and unify saved_object / serializer types * Cleanup * Address code review feedback * Don't mock errors helpers in SavedObjectsClient Mock * Address CR feedback * CR Feedback #2 * Add kibana-platform as code owners of Saved Objects * Better typings for SavedObjectsClient.errors * Use unknown as default for generic type request paramater * Bump @types/elasticsearch * Fix types for isForbiddenError * Bump x-pack @types/elasticsearch --- package.json | 2 +- src/legacy/server/kbn_server.js | 2 +- .../get_sorted_objects_for_export.test.ts | 39 +- .../export/get_sorted_objects_for_export.ts | 6 +- .../export/inject_nested_depdendencies.ts | 4 +- .../import/import_saved_objects.ts | 4 +- .../import/resolve_import_errors.ts | 4 +- .../import/validate_references.ts | 6 +- .../saved_objects/{index.js => index.ts} | 7 +- .../migrations/core/document_migrator.ts | 3 +- .../migrations/core/elastic_index.ts | 5 +- .../saved_objects/routes/bulk_create.test.ts | 17 +- .../saved_objects/routes/bulk_create.ts | 4 +- .../server/saved_objects/routes/types.ts | 4 +- .../saved_objects/schema/schema.mock.ts | 1 + .../server/saved_objects/schema/schema.ts | 9 +- .../saved_objects/serialization/index.ts | 22 +- .../server/saved_objects/service/index.js | 21 -- .../service/{index.d.ts => index.ts} | 17 +- ...rror.test.js => decorate_es_error.test.ts} | 8 +- ...orate_es_error.js => decorate_es_error.ts} | 15 +- .../saved_objects/service/lib/errors.d.ts | 30 -- .../lib/{errors.test.js => errors.test.ts} | 21 +- .../service/lib/{errors.js => errors.ts} | 78 ++-- ...fields.test.js => included_fields.test.ts} | 65 +++- ...{included_fields.js => included_fields.ts} | 23 +- .../server/saved_objects/service/lib/index.js | 24 -- .../service/lib/{index.d.ts => index.ts} | 8 +- .../saved_objects/service/lib/repository.d.ts | 59 --- .../service/lib/repository.test.js | 141 ++++--- .../lib/{repository.js => repository.ts} | 352 ++++++++++-------- .../service/lib/scoped_client_provider.d.ts | 39 -- ..._provider.js => scoped_client_provider.ts} | 37 +- .../service/lib/search_dsl/search_dsl.ts | 2 +- .../service/saved_objects_client.d.ts | 149 -------- .../service/saved_objects_client.js | 202 ---------- .../saved_objects_client.mock.ts} | 24 +- .../service/saved_objects_client.ts | 307 +++++++++++++++ .../error_auto_create_index.test.js | 4 +- .../error_auto_create_index.ts | 3 +- .../saved_objects/saved_objects_client.ts | 17 +- .../saved_objects/simple_saved_object.ts | 2 +- x-pack/package.json | 6 +- ...ypted_saved_objects_client_wrapper.test.ts | 20 +- .../encrypted_saved_objects_client_wrapper.ts | 32 +- .../spaces_saved_objects_client.ts | 20 +- .../server/lib/reindexing/reindex_actions.ts | 6 +- x-pack/test/typings/index.d.ts | 5 + x-pack/typings/index.d.ts | 11 + yarn.lock | 20 +- 50 files changed, 931 insertions(+), 976 deletions(-) rename src/legacy/server/saved_objects/{index.js => index.ts} (85%) delete mode 100644 src/legacy/server/saved_objects/service/index.js rename src/legacy/server/saved_objects/service/{index.d.ts => index.ts} (83%) rename src/legacy/server/saved_objects/service/lib/{decorate_es_error.test.js => decorate_es_error.test.ts} (100%) rename src/legacy/server/saved_objects/service/lib/{decorate_es_error.js => decorate_es_error.ts} (93%) delete mode 100644 src/legacy/server/saved_objects/service/lib/errors.d.ts rename src/legacy/server/saved_objects/service/lib/{errors.test.js => errors.test.ts} (99%) rename src/legacy/server/saved_objects/service/lib/{errors.js => errors.ts} (53%) rename src/legacy/server/saved_objects/service/lib/{included_fields.test.js => included_fields.test.ts} (79%) rename src/legacy/server/saved_objects/service/lib/{included_fields.js => included_fields.ts} (69%) delete mode 100644 src/legacy/server/saved_objects/service/lib/index.js rename src/legacy/server/saved_objects/service/lib/{index.d.ts => index.ts} (96%) delete mode 100644 src/legacy/server/saved_objects/service/lib/repository.d.ts rename src/legacy/server/saved_objects/service/lib/{repository.js => repository.ts} (68%) delete mode 100644 src/legacy/server/saved_objects/service/lib/scoped_client_provider.d.ts rename src/legacy/server/saved_objects/service/lib/{scoped_client_provider.js => scoped_client_provider.ts} (57%) delete mode 100644 src/legacy/server/saved_objects/service/saved_objects_client.d.ts delete mode 100644 src/legacy/server/saved_objects/service/saved_objects_client.js rename src/legacy/server/saved_objects/{index.d.ts => service/saved_objects_client.mock.ts} (67%) create mode 100644 src/legacy/server/saved_objects/service/saved_objects_client.ts diff --git a/package.json b/package.json index 84709872bd1d262..1b215eec8e9194c 100644 --- a/package.json +++ b/package.json @@ -282,7 +282,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 c6396a8f009a96b..78a8829dbb08a2a 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 4122fa63585e0c7..7ac3ebd412c0a99 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 dc48e35dc5898f1..9782de4b553f8b8 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 8529dcae4f0a6f1..ee9ce781ef9a5d6 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 03be32392b80307..10c1350c4c579bf 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 77fe856ece9ff6a..5cd4d2fca740c74 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 e44cacd99248936..2e3c1ef5293b303 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 e128322850a6e29..e6e9e2d2660004f 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 636b35e390ef043..bcff2988f4afefe 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 48d44492eb2757b..1e55bd3d01688b3 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 5c69c490084b311..f981b0a62f605b5 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 ffc6831bc3e5f6a..35b90ecbcd85363 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 8a63a5ff32d9aa5..f658a6111789057 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 844893156deef43..7fec7d54294d6cc 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 14a613835569d36..6756feeb15a0f6b 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 997c5d38b69db25..72071ae8866fae3 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 4624197e323e3b5..000000000000000 --- 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 cfc190bb199aa59..c4e0d66eb95b891 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 8d070e17132027d..272a26327b80808 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 c99ca7c79458456..becb41b78dad4aa 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 cfeed417b98da79..000000000000000 --- 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 b9b437f345d7d6c..facbacae84d07bc 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 63fed3f092bccb2..e1df155022b113c 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 d0b01638aff1a40..40d6552c2ad5f31 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 ce972d89afeae65..f372db5a1a63515 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 5851fc8568e4c0b..000000000000000 --- 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 486d3b1b46bb187..68fa24058410039 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 af2f7d04126f2f6..000000000000000 --- 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 37941bcc1814eb9..29ccdb3b8002aff 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 c5cfce72b670202..ef4b17f5106c94a 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 10f3cce763f9a49..000000000000000 --- 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 4049dcbf49f7476..201b316005d7cec 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 5baf46e2edab7b4..83e06eb17ccf2d7 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 cb9ace7bcb1baa3..000000000000000 --- 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 f98b99688bf2342..000000000000000 --- 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 e49dc27b9598aa4..16de6e4eb5b524f 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 000000000000000..a0d378bfc5a9726 --- /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 b583133446712ed..92322fb1c7da639 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 @@ -77,7 +77,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 e5287f18e1f7a6d..09c6bfd93148f91 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 fe94afc7f14f27c..6fee8826d16380d 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 4bb20efdf43fa39..d742b103afdd0f8 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 6a82e6fbec6fcfb..1cb93b9f66971d9 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,13 +260,13 @@ "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.14.3", + "monaco-editor": "^0.17.0", "ngreact": "^0.5.1", "nock": "10.0.4", "node-fetch": "^2.1.2", 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 adb41a2eb209467..2ea2e55eee8f576 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 6afaa9b8f769f62..35fa46f4e37396e 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 b7326e1f344a4cd..c1873e52a2b8eb2 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 ba50e659f1e205d..70e33bc406ac4eb 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 0688ef9e4d8ec82..b365d09392edd26 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 0688ef9e4d8ec82..69e7bf130210df0 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 f0fb21421718e9b..b91191191bb21cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3361,10 +3361,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" @@ -26562,11 +26562,21 @@ typescript-fsa@^2.0.0, typescript-fsa@^2.5.0: resolved "https://registry.yarnpkg.com/typescript-fsa/-/typescript-fsa-2.5.0.tgz#1baec01b5e8f5f34c322679d1327016e9e294faf" integrity sha1-G67AG16PXzTDImedEycBbp4pT68= -typescript@^3.3.3333, typescript@~3.0.3, typescript@~3.3.3333, typescript@~3.4.3: +typescript@^3.3.3333, typescript@~3.3.3333: version "3.3.3333" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.3333.tgz#171b2c5af66c59e9431199117a3bcadc66fdcfd6" integrity sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw== +typescript@~3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.0.3.tgz#4853b3e275ecdaa27f78fda46dc273a7eb7fc1c8" + integrity sha512-kk80vLW9iGtjMnIv11qyxLqZm20UklzuR2tL0QAnDIygIUIemcZMxlMWudl9OOt76H3ntVzcTiddQ1/pAAJMYg== + +typescript@~3.4.3: + version "3.4.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.5.tgz#2d2618d10bb566572b8d7aad5180d84257d70a99" + integrity sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw== + typings-tester@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/typings-tester/-/typings-tester-0.3.2.tgz#04cc499d15ab1d8b2d14dd48415a13d01333bc5b"