mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[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
This commit is contained in:
parent
d43ee7fe3d
commit
ea9721ad13
51 changed files with 933 additions and 976 deletions
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
|
@ -29,6 +29,8 @@
|
|||
|
||||
# Platform
|
||||
/src/core/ @elastic/kibana-platform
|
||||
/src/legacy/server/saved_objects/ @elastic/kibana-platform
|
||||
/src/legacy/ui/public/saved_objects @elastic/kibana-platform
|
||||
|
||||
# Security
|
||||
/x-pack/plugins/security/ @elastic/kibana-security
|
||||
|
|
|
@ -285,7 +285,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",
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<string, SavedObject>) {
|
||||
const objectsToFetch = new Map<string, { type: string; id: string }>();
|
||||
|
@ -34,7 +34,7 @@ export function getObjectReferencesToFetch(savedObjectsMap: Map<string, SavedObj
|
|||
|
||||
export async function injectNestedDependencies(
|
||||
savedObjects: SavedObject[],
|
||||
savedObjectsClient: SavedObjectsClient
|
||||
savedObjectsClient: SavedObjectsClientContract
|
||||
) {
|
||||
const savedObjectsMap = new Map<string, SavedObject>();
|
||||
for (const savedObject of savedObjects) {
|
||||
|
|
|
@ -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[];
|
||||
}
|
||||
|
||||
|
|
|
@ -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[];
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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';
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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: [],
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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<Hapi.Request> {
|
||||
pre: {
|
||||
savedObjectsClient: SavedObjectsClient;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
};
|
||||
query: {
|
||||
overwrite: boolean;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import { SavedObjectsSchema } from './schema';
|
|||
type Schema = PublicMethodsOf<SavedObjectsSchema>;
|
||||
const createSchemaMock = () => {
|
||||
const mocked: jest.Mocked<Schema> = {
|
||||
getIndexForType: jest.fn().mockReturnValue('.kibana-test'),
|
||||
isHiddenType: jest.fn().mockReturnValue(false),
|
||||
isNamespaceAgnostic: jest.fn((type: string) => type === 'global'),
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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';
|
|
@ -31,15 +31,10 @@ export interface SavedObjectsService<Request = any> {
|
|||
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';
|
|
@ -21,13 +21,13 @@ import { errors as esErrors } from 'elasticsearch';
|
|||
|
||||
import { decorateEsError } from './decorate_es_error';
|
||||
import {
|
||||
isEsUnavailableError,
|
||||
isConflictError,
|
||||
isNotAuthorizedError,
|
||||
isForbiddenError,
|
||||
isRequestEntityTooLargeError,
|
||||
isNotFoundError,
|
||||
isBadRequestError,
|
||||
isConflictError,
|
||||
isEsUnavailableError,
|
||||
isForbiddenError,
|
||||
isNotAuthorizedError,
|
||||
isNotFoundError,
|
||||
isRequestEntityTooLargeError,
|
||||
} from './errors';
|
||||
|
||||
describe('savedObjectsClient/decorateEsError', () => {
|
|
@ -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 ||
|
|
@ -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;
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
|
@ -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')
|
|
@ -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 };
|
|
@ -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 };
|
|
@ -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<SavedObject>;
|
||||
public incrementCounter: (
|
||||
type: string,
|
||||
id: string,
|
||||
counterFieldName: string,
|
||||
options?: BaseOptions
|
||||
) => Promise<SavedObject>;
|
||||
|
||||
constructor(options: SavedObjectsRepositoryOptions);
|
||||
}
|
|
@ -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: {
|
||||
|
|
|
@ -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<T> = {
|
||||
tag: 'Left';
|
||||
error: T;
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-interface
|
||||
type Right<T> = {
|
||||
tag: 'Right';
|
||||
value: T;
|
||||
};
|
||||
|
||||
type Either<L, R> = Left<L> | Right<R>;
|
||||
const isLeft = <L, R>(either: Either<L, R>): either is Left<L> => {
|
||||
return either.tag === 'Left';
|
||||
};
|
||||
|
||||
export interface SavedObjectsRepositoryOptions {
|
||||
index: string;
|
||||
mappings: IndexMapping;
|
||||
callCluster: CallCluster;
|
||||
schema: SavedObjectsSchema;
|
||||
serializer: SavedObjectsSerializer;
|
||||
migrator: KibanaMigrator;
|
||||
allowedTypes: string[];
|
||||
onBeforeWrite?: (...args: Parameters<CallCluster>) => Promise<void>;
|
||||
}
|
||||
|
||||
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<CallCluster>) => Promise<void>;
|
||||
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<CallCluster>) => {
|
||||
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;
|
||||
public async create<T extends SavedObjectAttributes>(
|
||||
type: string,
|
||||
attributes: T,
|
||||
options: CreateOptions = { overwrite: false, references: [] }
|
||||
): Promise<SavedObject<T>> {
|
||||
const { id, migrationVersion, overwrite, namespace, references } = options;
|
||||
|
||||
if (!this._isTypeAllowed(type)) {
|
||||
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<T extends SavedObjectAttributes = any>(
|
||||
objects: Array<BulkCreateObject<T>>,
|
||||
options: CreateOptions = {}
|
||||
): Promise<BulkResponse<T>> {
|
||||
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<Either<any, any>> = 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<any> {
|
||||
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<T extends SavedObjectAttributes = any>({
|
||||
search,
|
||||
defaultSearchOperator = 'OR',
|
||||
searchFields,
|
||||
hasReference,
|
||||
page = 1,
|
||||
perPage = 20,
|
||||
sortField,
|
||||
sortOrder,
|
||||
fields,
|
||||
namespace,
|
||||
type,
|
||||
}: FindOptions): Promise<FindResponse<T>> {
|
||||
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<T extends SavedObjectAttributes = any>(
|
||||
objects: BulkGetObject[] = [],
|
||||
options: BaseOptions = {}
|
||||
): Promise<BulkResponse<T>> {
|
||||
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<T>;
|
||||
});
|
||||
|
||||
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<T>;
|
||||
}
|
||||
|
||||
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<T extends SavedObjectAttributes = any>(
|
||||
type: string,
|
||||
id: string,
|
||||
options: BaseOptions = {}
|
||||
): Promise<SavedObject<T>> {
|
||||
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<T extends SavedObjectAttributes = any>(
|
||||
type: string,
|
||||
id: string,
|
||||
attributes: Partial<T>,
|
||||
options: UpdateOptions = {}
|
||||
): Promise<UpdateResponse<T>> {
|
||||
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<CallCluster>) {
|
||||
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<CallCluster>) {
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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<Request = any> {
|
||||
client: SavedObjectsClient;
|
||||
request: Request;
|
||||
}
|
||||
|
||||
export type SavedObjectsClientWrapperFactory<Request = any> = (
|
||||
options: SavedObjectsClientWrapperOptions<Request>
|
||||
) => SavedObjectsClient;
|
||||
|
||||
export interface ScopedSavedObjectsClientProvider<Request = any> {
|
||||
// ATTENTION: these types are incomplete
|
||||
|
||||
addClientWrapperFactory(
|
||||
priority: number,
|
||||
wrapperFactory: SavedObjectsClientWrapperFactory<Request>
|
||||
): void;
|
||||
getClient(request: Request): SavedObjectsClient;
|
||||
}
|
|
@ -17,22 +17,47 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { PriorityCollection } from './priority_collection';
|
||||
import { SavedObjectsClientContract } from '..';
|
||||
|
||||
export interface SavedObjectsClientWrapperOptions<Request = unknown> {
|
||||
client: SavedObjectsClientContract;
|
||||
request: Request;
|
||||
}
|
||||
|
||||
export type SavedObjectsClientWrapperFactory<Request = unknown> = (
|
||||
options: SavedObjectsClientWrapperOptions<Request>
|
||||
) => SavedObjectsClientContract;
|
||||
|
||||
export type SavedObjectsClientFactory<Request = unknown> = (
|
||||
{ request }: { request: Request }
|
||||
) => SavedObjectsClientContract;
|
||||
|
||||
/**
|
||||
* Provider for the Scoped Saved Object Client.
|
||||
*/
|
||||
export class ScopedSavedObjectsClientProvider {
|
||||
_wrapperFactories = new PriorityCollection();
|
||||
export class ScopedSavedObjectsClientProvider<Request = unknown> {
|
||||
private readonly _wrapperFactories = new PriorityCollection<
|
||||
SavedObjectsClientWrapperFactory<Request>
|
||||
>();
|
||||
private _clientFactory: SavedObjectsClientFactory<Request>;
|
||||
private readonly _originalClientFactory: SavedObjectsClientFactory<Request>;
|
||||
|
||||
constructor({ defaultClientFactory }) {
|
||||
constructor({
|
||||
defaultClientFactory,
|
||||
}: {
|
||||
defaultClientFactory: SavedObjectsClientFactory<Request>;
|
||||
}) {
|
||||
this._originalClientFactory = this._clientFactory = defaultClientFactory;
|
||||
}
|
||||
|
||||
addClientWrapperFactory(priority, wrapperFactory) {
|
||||
addClientWrapperFactory(
|
||||
priority: number,
|
||||
wrapperFactory: SavedObjectsClientWrapperFactory<Request>
|
||||
): 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,
|
||||
});
|
|
@ -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[];
|
||||
|
|
|
@ -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<T extends SavedObjectAttributes = any> {
|
||||
id?: string;
|
||||
type: string;
|
||||
attributes: T;
|
||||
extraDocumentProperties?: string[];
|
||||
}
|
||||
|
||||
export interface BulkCreateResponse<T extends SavedObjectAttributes = any> {
|
||||
saved_objects: Array<SavedObject<T>>;
|
||||
}
|
||||
|
||||
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<T extends SavedObjectAttributes = any> {
|
||||
saved_objects: Array<SavedObject<T>>;
|
||||
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<T extends SavedObjectAttributes = any> {
|
||||
saved_objects: Array<SavedObject<T>>;
|
||||
}
|
||||
|
||||
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<T extends SavedObjectAttributes = any> {
|
||||
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<T extends SavedObjectAttributes = any> = SavedObject<T>;
|
||||
export type CreateResponse<T extends SavedObjectAttributes = any> = SavedObject<T>;
|
||||
export type UpdateResponse<T extends SavedObjectAttributes = any> = SavedObject<T>;
|
||||
|
||||
export declare class SavedObjectsClient {
|
||||
public static errors: typeof errors;
|
||||
public errors: typeof errors;
|
||||
|
||||
constructor(repository: SavedObjectsRepository);
|
||||
|
||||
public create<T extends SavedObjectAttributes = any>(
|
||||
type: string,
|
||||
attributes: T,
|
||||
options?: CreateOptions
|
||||
): Promise<CreateResponse<T>>;
|
||||
public bulkCreate<T extends SavedObjectAttributes = any>(
|
||||
objects: Array<BulkCreateObject<T>>,
|
||||
options?: CreateOptions
|
||||
): Promise<BulkCreateResponse<T>>;
|
||||
public delete(type: string, id: string, options?: BaseOptions): Promise<{}>;
|
||||
public find<T extends SavedObjectAttributes = any>(
|
||||
options: FindOptions
|
||||
): Promise<FindResponse<T>>;
|
||||
public bulkGet<T extends SavedObjectAttributes = any>(
|
||||
objects: BulkGetObjects,
|
||||
options?: BaseOptions
|
||||
): Promise<BulkGetResponse<T>>;
|
||||
public get<T extends SavedObjectAttributes = any>(
|
||||
type: string,
|
||||
id: string,
|
||||
options?: BaseOptions
|
||||
): Promise<GetResponse<T>>;
|
||||
public update<T extends SavedObjectAttributes = any>(
|
||||
type: string,
|
||||
id: string,
|
||||
attributes: Partial<T>,
|
||||
options?: UpdateOptions
|
||||
): Promise<UpdateResponse<T>>;
|
||||
}
|
|
@ -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<string>)} [options.type]
|
||||
* @property {string} [options.search]
|
||||
* @property {string} [options.defaultSearchOperator]
|
||||
* @property {Array<string>} [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<string>} [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);
|
||||
}
|
||||
}
|
|
@ -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<SavedObjectsClientContract> => ({
|
||||
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 };
|
307
src/legacy/server/saved_objects/service/saved_objects_client.ts
Normal file
307
src/legacy/server/saved_objects/service/saved_objects_client.ts
Normal file
|
@ -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<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
||||
|
||||
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<T extends SavedObjectAttributes = any> {
|
||||
id?: string;
|
||||
type: string;
|
||||
attributes: T;
|
||||
references?: SavedObjectReference[];
|
||||
migrationVersion?: MigrationVersion;
|
||||
}
|
||||
|
||||
export interface BulkResponse<T extends SavedObjectAttributes = any> {
|
||||
saved_objects: Array<SavedObject<T>>;
|
||||
}
|
||||
|
||||
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<T extends SavedObjectAttributes = any> {
|
||||
saved_objects: Array<SavedObject<T>>;
|
||||
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<T extends SavedObjectAttributes = any> {
|
||||
saved_objects: Array<SavedObject<T>>;
|
||||
}
|
||||
|
||||
export interface UpdateResponse<T extends SavedObjectAttributes = any>
|
||||
extends Omit<SavedObject<T>, 'attributes'> {
|
||||
attributes: Partial<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<T extends SavedObjectAttributes = any> {
|
||||
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<SavedObjectsClient, keyof SavedObjectsClient>;
|
||||
|
||||
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<T extends SavedObjectAttributes = any>(
|
||||
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<T extends SavedObjectAttributes = any>(
|
||||
objects: Array<BulkCreateObject<T>>,
|
||||
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<T extends SavedObjectAttributes = any>(
|
||||
options: FindOptions
|
||||
): Promise<FindResponse<T>> {
|
||||
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<T extends SavedObjectAttributes = any>(
|
||||
objects: BulkGetObject[] = [],
|
||||
options: BaseOptions = {}
|
||||
): Promise<BulkResponse<T>> {
|
||||
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<T extends SavedObjectAttributes = any>(
|
||||
type: string,
|
||||
id: string,
|
||||
options: BaseOptions = {}
|
||||
): Promise<SavedObject<T>> {
|
||||
return await this._repository.get(type, id, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an SavedObject
|
||||
*
|
||||
* @param type
|
||||
* @param id
|
||||
* @param options
|
||||
*/
|
||||
async update<T extends SavedObjectAttributes = any>(
|
||||
type: string,
|
||||
id: string,
|
||||
attributes: Partial<T>,
|
||||
options: UpdateOptions = {}
|
||||
): Promise<UpdateResponse<T>> {
|
||||
return await this._repository.update(type, id, attributes, options);
|
||||
}
|
||||
}
|
|
@ -73,7 +73,9 @@ describe('isAutoCreateIndexError correctly handles KFetchError thrown by kfetch'
|
|||
matcher: '*',
|
||||
response: {
|
||||
body: {
|
||||
code: 'ES_AUTO_CREATE_INDEX_ERROR',
|
||||
attributes: {
|
||||
code: 'ES_AUTO_CREATE_INDEX_ERROR',
|
||||
},
|
||||
},
|
||||
status: 503,
|
||||
},
|
||||
|
|
|
@ -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'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<T extends SavedObjectAttributes = SavedObjectAttributes>
|
|||
interface BatchQueueEntry {
|
||||
type: string;
|
||||
id: string;
|
||||
resolve: <T extends SavedObjectAttributes>(
|
||||
value: SimpleSavedObject<T> | PlainSavedObject<T>
|
||||
) => void;
|
||||
resolve: <T extends SavedObjectAttributes>(value: SimpleSavedObject<T> | SavedObject<T>) => void;
|
||||
reject: (reason?: any) => void;
|
||||
}
|
||||
|
||||
|
@ -165,7 +163,7 @@ export class SavedObjectsClient {
|
|||
overwrite: options.overwrite,
|
||||
};
|
||||
|
||||
const createRequest: Promise<CreateResponse<T>> = this.request({
|
||||
const createRequest: Promise<SavedObject<T>> = this.request({
|
||||
method: 'POST',
|
||||
path,
|
||||
query,
|
||||
|
@ -334,18 +332,17 @@ export class SavedObjectsClient {
|
|||
version,
|
||||
};
|
||||
|
||||
const request: Promise<UpdateResponse<T>> = this.request({
|
||||
return this.request({
|
||||
method: 'PUT',
|
||||
path,
|
||||
body,
|
||||
});
|
||||
return request.then(resp => {
|
||||
}).then((resp: SavedObject<T>) => {
|
||||
return this.createSavedObject(resp);
|
||||
});
|
||||
}
|
||||
|
||||
private createSavedObject<T extends SavedObjectAttributes>(
|
||||
options: PlainSavedObject<T>
|
||||
options: SavedObject<T>
|
||||
): SimpleSavedObject<T> {
|
||||
return new SimpleSavedObject(this, options);
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ export class SimpleSavedObject<T extends SavedObjectAttributes> {
|
|||
return has(this.attributes, key);
|
||||
}
|
||||
|
||||
public save() {
|
||||
public save(): Promise<SimpleSavedObject<T>> {
|
||||
if (this.id) {
|
||||
return this.client.update(this.type, this.id, this.attributes, {
|
||||
migrationVersion: this.migrationVersion,
|
||||
|
|
|
@ -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",
|
||||
|
@ -262,12 +262,12 @@
|
|||
"mapbox-gl": "0.54.0",
|
||||
"mapbox-gl-draw-rectangle-mode": "^1.0.4",
|
||||
"markdown-it": "^8.4.1",
|
||||
"memoize-one": "^5.0.0",
|
||||
"mime": "^2.2.2",
|
||||
"mkdirp": "0.5.1",
|
||||
"moment": "^2.20.1",
|
||||
"moment-duration-format": "^1.3.0",
|
||||
"moment-timezone": "^0.5.14",
|
||||
"memoize-one": "^5.0.0",
|
||||
"monaco-editor": "^0.17.0",
|
||||
"ngreact": "^0.5.1",
|
||||
"nock": "10.0.4",
|
||||
|
|
|
@ -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<SavedObjectsClient> {
|
||||
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<SavedObjectsClient>;
|
||||
let mockBaseClient: jest.Mocked<SavedObjectsClientContract>;
|
||||
let encryptedSavedObjectsServiceMock: jest.Mocked<EncryptedSavedObjectsService>;
|
||||
beforeEach(() => {
|
||||
mockBaseClient = createSavedObjectsClientMock();
|
||||
mockBaseClient = SavedObjectsClientMock.create();
|
||||
encryptedSavedObjectsServiceMock = createEncryptedSavedObjectsServiceMock([
|
||||
{
|
||||
type: 'known-type',
|
||||
|
|
|
@ -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<EncryptedSavedObjectsService>;
|
||||
}
|
||||
|
||||
|
@ -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<T extends SavedObjectAttributes>(
|
||||
|
@ -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<T extends UpdateResponse | SavedObject>(
|
||||
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<T extends BulkResponse | FindResponse>(
|
||||
response: T
|
||||
): T {
|
||||
for (const savedObject of response.saved_objects) {
|
||||
if (this.options.service.isRegistered(savedObject.type)) {
|
||||
savedObject.attributes = this.options.service.stripEncryptedAttributes(
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -140,7 +140,7 @@ export const reindexActionsFactory = (
|
|||
reindexOp.id,
|
||||
{ ...reindexOp.attributes, locked: moment().format() },
|
||||
{ version: reindexOp.version }
|
||||
);
|
||||
) as Promise<ReindexSavedObject>;
|
||||
};
|
||||
|
||||
const releaseLock = (reindexOp: ReindexSavedObject) => {
|
||||
|
@ -149,7 +149,7 @@ export const reindexActionsFactory = (
|
|||
reindexOp.id,
|
||||
{ ...reindexOp.attributes, locked: null },
|
||||
{ version: reindexOp.version }
|
||||
);
|
||||
) as Promise<ReindexSavedObject>;
|
||||
};
|
||||
|
||||
// ----- Public interface
|
||||
|
@ -180,7 +180,7 @@ export const reindexActionsFactory = (
|
|||
const newAttrs = { ...reindexOp.attributes, locked: moment().format(), ...attrs };
|
||||
return client.update<ReindexOperation>(REINDEX_OP_TYPE, reindexOp.id, newAttrs, {
|
||||
version: reindexOp.version,
|
||||
});
|
||||
}) as Promise<ReindexSavedObject>;
|
||||
},
|
||||
|
||||
async runWhileLocked(reindexOp, func) {
|
||||
|
|
5
x-pack/test/typings/index.d.ts
vendored
5
x-pack/test/typings/index.d.ts
vendored
|
@ -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;
|
||||
}
|
||||
|
|
11
x-pack/typings/index.d.ts
vendored
11
x-pack/typings/index.d.ts
vendored
|
@ -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<T> = {
|
||||
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never
|
||||
}[keyof T];
|
||||
|
||||
type PublicMethodsOf<T> = Pick<T, MethodKeysOf<T>>;
|
||||
|
|
20
yarn.lock
20
yarn.lock
|
@ -3376,10 +3376,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"
|
||||
|
@ -26639,11 +26639,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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue