mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Migrate visualization
, annotation
, graph
to saved_object_content_storage
(#168520)
## Summary Close #167421 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
fe7afbbcb3
commit
bab28dc95d
22 changed files with 142 additions and 969 deletions
|
@ -129,7 +129,7 @@ export type UpdateArgsToSoUpdateOptions<Types extends CMCrudTypes> = (
|
|||
params: Types['UpdateOptions']
|
||||
) => SavedObjectsUpdateOptions<Types['Attributes']>;
|
||||
|
||||
export interface SOContentStorageConstrutorParams<Types extends CMCrudTypes> {
|
||||
export interface SOContentStorageConstructorParams<Types extends CMCrudTypes> {
|
||||
savedObjectType: string;
|
||||
cmServicesDefinition: ServicesDefinitionSet;
|
||||
// this is necessary since unexpected saved object attributes could cause schema validation to fail
|
||||
|
@ -137,6 +137,12 @@ export interface SOContentStorageConstrutorParams<Types extends CMCrudTypes> {
|
|||
createArgsToSoCreateOptions?: CreateArgsToSoCreateOptions<Types>;
|
||||
updateArgsToSoUpdateOptions?: UpdateArgsToSoUpdateOptions<Types>;
|
||||
searchArgsToSOFindOptions?: SearchArgsToSOFindOptions<Types>;
|
||||
/**
|
||||
* MSearch is a feature that allows searching across multiple content types
|
||||
* (for example, could be used in a general content finder or the like)
|
||||
*
|
||||
* defaults to false
|
||||
*/
|
||||
enableMSearch?: boolean;
|
||||
mSearchAdditionalSearchFields?: string[];
|
||||
|
||||
|
@ -163,7 +169,7 @@ export abstract class SOContentStorage<Types extends CMCrudTypes>
|
|||
mSearchAdditionalSearchFields,
|
||||
logger,
|
||||
throwOnResultValidationError,
|
||||
}: SOContentStorageConstrutorParams<Types>) {
|
||||
}: SOContentStorageConstructorParams<Types>) {
|
||||
this.logger = logger;
|
||||
this.throwOnResultValidationError = throwOnResultValidationError ?? false;
|
||||
this.savedObjectType = savedObjectType;
|
||||
|
@ -219,8 +225,8 @@ export abstract class SOContentStorage<Types extends CMCrudTypes>
|
|||
|
||||
private throwOnResultValidationError: boolean;
|
||||
private logger: Logger;
|
||||
private savedObjectType: SOContentStorageConstrutorParams<Types>['savedObjectType'];
|
||||
private cmServicesDefinition: SOContentStorageConstrutorParams<Types>['cmServicesDefinition'];
|
||||
private savedObjectType: SOContentStorageConstructorParams<Types>['savedObjectType'];
|
||||
private cmServicesDefinition: SOContentStorageConstructorParams<Types>['cmServicesDefinition'];
|
||||
private createArgsToSoCreateOptions: CreateArgsToSoCreateOptions<Types>;
|
||||
private updateArgsToSoUpdateOptions: UpdateArgsToSoUpdateOptions<Types>;
|
||||
private searchArgsToSOFindOptions: SearchArgsToSOFindOptions<Types>;
|
||||
|
|
|
@ -27,6 +27,7 @@ export type {
|
|||
EventAnnotationGroupSearchIn,
|
||||
EventAnnotationGroupSearchOut,
|
||||
EventAnnotationGroupSearchQuery,
|
||||
EventAnnotationGroupCrudTypes,
|
||||
} from './latest';
|
||||
|
||||
export * as EventAnnotationGroupV1 from './v1';
|
||||
|
|
|
@ -23,5 +23,6 @@ export type {
|
|||
EventAnnotationGroupSearchIn,
|
||||
EventAnnotationGroupSearchOut,
|
||||
EventAnnotationGroupSearchQuery,
|
||||
EventAnnotationGroupCrudTypes,
|
||||
Reference,
|
||||
} from './types';
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
CreateResult,
|
||||
UpdateResult,
|
||||
} from '@kbn/content-management-plugin/common';
|
||||
import { ContentManagementCrudTypes } from '@kbn/content-management-utils';
|
||||
|
||||
import type { DataViewSpec } from '@kbn/data-views-plugin/common';
|
||||
import type { EventAnnotationConfig } from '@kbn/event-annotation-common';
|
||||
|
@ -125,3 +126,13 @@ export type EventAnnotationGroupSearchIn = SearchIn<
|
|||
>;
|
||||
|
||||
export type EventAnnotationGroupSearchOut = SearchResult<EventAnnotationGroupSavedObject>;
|
||||
|
||||
// ----------- CRUD TYPES --------------
|
||||
|
||||
export type EventAnnotationGroupCrudTypes = ContentManagementCrudTypes<
|
||||
EventAnnotationGroupContentType,
|
||||
EventAnnotationGroupSavedObjectAttributes,
|
||||
CreateOptions,
|
||||
UpdateOptions,
|
||||
{}
|
||||
>;
|
||||
|
|
|
@ -37,6 +37,7 @@ export type {
|
|||
EventAnnotationGroupSearchOut,
|
||||
EventAnnotationGroupDeleteIn,
|
||||
EventAnnotationGroupDeleteOut,
|
||||
EventAnnotationGroupCrudTypes,
|
||||
} from './content_management';
|
||||
export { CONTENT_ID } from './content_management';
|
||||
export { ANNOTATIONS_LISTING_VIEW_ID } from './constants';
|
||||
|
|
|
@ -5,336 +5,34 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import Boom from '@hapi/boom';
|
||||
import type { SearchQuery } from '@kbn/content-management-plugin/common';
|
||||
import type { ContentStorage, StorageContext } from '@kbn/content-management-plugin/server';
|
||||
import type {
|
||||
SavedObject,
|
||||
SavedObjectReference,
|
||||
SavedObjectsFindOptions,
|
||||
} from '@kbn/core-saved-objects-api-server';
|
||||
|
||||
import { getMSearch, type GetMSearchType } from '@kbn/content-management-utils';
|
||||
import { SOContentStorage } from '@kbn/content-management-utils';
|
||||
|
||||
import { EVENT_ANNOTATION_GROUP_TYPE } from '@kbn/event-annotation-common';
|
||||
import { Logger } from '@kbn/logging';
|
||||
import { cmServicesDefinition } from '../../common/content_management/cm_services';
|
||||
import type {
|
||||
EventAnnotationGroupSavedObjectAttributes,
|
||||
EventAnnotationGroupSavedObject,
|
||||
PartialEventAnnotationGroupSavedObject,
|
||||
EventAnnotationGroupGetOut,
|
||||
EventAnnotationGroupCreateIn,
|
||||
EventAnnotationGroupCreateOut,
|
||||
CreateOptions,
|
||||
EventAnnotationGroupUpdateIn,
|
||||
EventAnnotationGroupUpdateOut,
|
||||
UpdateOptions,
|
||||
EventAnnotationGroupDeleteOut,
|
||||
EventAnnotationGroupSearchQuery,
|
||||
EventAnnotationGroupSearchOut,
|
||||
} from '../../common/content_management';
|
||||
import type { EventAnnotationGroupCrudTypes } from '../../common/content_management';
|
||||
|
||||
const savedObjectClientFromRequest = async (ctx: StorageContext) => {
|
||||
if (!ctx.requestHandlerContext) {
|
||||
throw new Error('Storage context.requestHandlerContext missing.');
|
||||
}
|
||||
|
||||
const { savedObjects } = await ctx.requestHandlerContext.core;
|
||||
return savedObjects.client;
|
||||
};
|
||||
|
||||
type PartialSavedObject<T> = Omit<SavedObject<Partial<T>>, 'references'> & {
|
||||
references: SavedObjectReference[] | undefined;
|
||||
};
|
||||
|
||||
function savedObjectToEventAnnotationGroupSavedObject(
|
||||
savedObject: SavedObject<EventAnnotationGroupSavedObjectAttributes>,
|
||||
partial: false
|
||||
): EventAnnotationGroupSavedObject;
|
||||
|
||||
function savedObjectToEventAnnotationGroupSavedObject(
|
||||
savedObject: PartialSavedObject<EventAnnotationGroupSavedObjectAttributes>,
|
||||
partial: true
|
||||
): PartialEventAnnotationGroupSavedObject;
|
||||
|
||||
function savedObjectToEventAnnotationGroupSavedObject(
|
||||
savedObject:
|
||||
| SavedObject<EventAnnotationGroupSavedObjectAttributes>
|
||||
| PartialSavedObject<EventAnnotationGroupSavedObjectAttributes>
|
||||
): EventAnnotationGroupSavedObject | PartialEventAnnotationGroupSavedObject {
|
||||
const {
|
||||
id,
|
||||
type,
|
||||
updated_at: updatedAt,
|
||||
created_at: createdAt,
|
||||
attributes: { title, description, annotations, ignoreGlobalFilters, dataViewSpec },
|
||||
references,
|
||||
error,
|
||||
namespaces,
|
||||
} = savedObject;
|
||||
|
||||
return {
|
||||
id,
|
||||
type,
|
||||
updatedAt,
|
||||
createdAt,
|
||||
attributes: {
|
||||
title,
|
||||
description,
|
||||
annotations,
|
||||
ignoreGlobalFilters,
|
||||
dataViewSpec,
|
||||
},
|
||||
references,
|
||||
error,
|
||||
namespaces,
|
||||
};
|
||||
}
|
||||
|
||||
const SO_TYPE = EVENT_ANNOTATION_GROUP_TYPE;
|
||||
|
||||
export class EventAnnotationGroupStorage
|
||||
implements
|
||||
ContentStorage<EventAnnotationGroupSavedObject, PartialEventAnnotationGroupSavedObject>
|
||||
{
|
||||
mSearch: GetMSearchType<EventAnnotationGroupSavedObject>;
|
||||
constructor() {
|
||||
this.mSearch = getMSearch<EventAnnotationGroupSavedObject, EventAnnotationGroupSearchOut>({
|
||||
savedObjectType: SO_TYPE,
|
||||
export class EventAnnotationGroupStorage extends SOContentStorage<EventAnnotationGroupCrudTypes> {
|
||||
constructor({
|
||||
logger,
|
||||
throwOnResultValidationError,
|
||||
}: {
|
||||
logger: Logger;
|
||||
throwOnResultValidationError: boolean;
|
||||
}) {
|
||||
super({
|
||||
savedObjectType: EVENT_ANNOTATION_GROUP_TYPE,
|
||||
cmServicesDefinition,
|
||||
enableMSearch: true,
|
||||
allowedSavedObjectAttributes: [
|
||||
'title',
|
||||
'description',
|
||||
'ignoreGlobalFilters',
|
||||
'annotations',
|
||||
'ignoreGlobalFilters',
|
||||
'dataViewSpec',
|
||||
],
|
||||
logger,
|
||||
throwOnResultValidationError,
|
||||
});
|
||||
}
|
||||
|
||||
async get(ctx: StorageContext, id: string): Promise<EventAnnotationGroupGetOut> {
|
||||
const {
|
||||
utils: { getTransforms },
|
||||
version: { request: requestVersion },
|
||||
} = ctx;
|
||||
const transforms = getTransforms(cmServicesDefinition, requestVersion);
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
|
||||
const {
|
||||
saved_object: savedObject,
|
||||
alias_purpose: aliasPurpose,
|
||||
alias_target_id: aliasTargetId,
|
||||
outcome,
|
||||
} = await soClient.resolve<EventAnnotationGroupSavedObjectAttributes>(SO_TYPE, id);
|
||||
|
||||
const response: EventAnnotationGroupGetOut = {
|
||||
item: savedObjectToEventAnnotationGroupSavedObject(savedObject, false),
|
||||
meta: {
|
||||
aliasPurpose,
|
||||
aliasTargetId,
|
||||
outcome,
|
||||
},
|
||||
};
|
||||
|
||||
// Validate DB response and DOWN transform to the request version
|
||||
const { value, error: resultError } = transforms.get.out.result.down<
|
||||
EventAnnotationGroupGetOut,
|
||||
EventAnnotationGroupGetOut
|
||||
>(response);
|
||||
|
||||
if (resultError) {
|
||||
throw Boom.badRequest(`Invalid response. ${resultError.message}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
async bulkGet(): Promise<never> {
|
||||
// Not implemented. EventAnnotationGroup does not use bulkGet
|
||||
throw new Error(`[bulkGet] has not been implemented. See EventAnnotationGroupStorage class.`);
|
||||
}
|
||||
|
||||
async create(
|
||||
ctx: StorageContext,
|
||||
data: EventAnnotationGroupCreateIn['data'],
|
||||
options: CreateOptions
|
||||
): Promise<EventAnnotationGroupCreateOut> {
|
||||
const {
|
||||
utils: { getTransforms },
|
||||
version: { request: requestVersion },
|
||||
} = ctx;
|
||||
const transforms = getTransforms(cmServicesDefinition, requestVersion);
|
||||
|
||||
// Validate input (data & options) & UP transform them to the latest version
|
||||
const { value: dataToLatest, error: dataError } = transforms.create.in.data.up<
|
||||
EventAnnotationGroupSavedObjectAttributes,
|
||||
EventAnnotationGroupSavedObjectAttributes
|
||||
>(data);
|
||||
if (dataError) {
|
||||
throw Boom.badRequest(`Invalid data. ${dataError.message}`);
|
||||
}
|
||||
|
||||
const { value: optionsToLatest, error: optionsError } = transforms.create.in.options.up<
|
||||
CreateOptions,
|
||||
CreateOptions
|
||||
>(options);
|
||||
if (optionsError) {
|
||||
throw Boom.badRequest(`Invalid options. ${optionsError.message}`);
|
||||
}
|
||||
|
||||
// Save data in DB
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
const savedObject = await soClient.create<EventAnnotationGroupSavedObjectAttributes>(
|
||||
SO_TYPE,
|
||||
dataToLatest,
|
||||
optionsToLatest
|
||||
);
|
||||
|
||||
// Validate DB response and DOWN transform to the request version
|
||||
const { value, error: resultError } = transforms.create.out.result.down<
|
||||
EventAnnotationGroupCreateOut,
|
||||
EventAnnotationGroupCreateOut
|
||||
>({
|
||||
item: savedObjectToEventAnnotationGroupSavedObject(savedObject, false),
|
||||
});
|
||||
|
||||
if (resultError) {
|
||||
throw Boom.badRequest(`Invalid response. ${resultError.message}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
async update(
|
||||
ctx: StorageContext,
|
||||
id: string,
|
||||
data: EventAnnotationGroupUpdateIn['data'],
|
||||
options: UpdateOptions
|
||||
): Promise<EventAnnotationGroupUpdateOut> {
|
||||
const {
|
||||
utils: { getTransforms },
|
||||
version: { request: requestVersion },
|
||||
} = ctx;
|
||||
const transforms = getTransforms(cmServicesDefinition, requestVersion);
|
||||
|
||||
// Validate input (data & options) & UP transform them to the latest version
|
||||
const { value: dataToLatest, error: dataError } = transforms.update.in.data.up<
|
||||
EventAnnotationGroupSavedObjectAttributes,
|
||||
EventAnnotationGroupSavedObjectAttributes
|
||||
>(data);
|
||||
if (dataError) {
|
||||
throw Boom.badRequest(`Invalid data. ${dataError.message}`);
|
||||
}
|
||||
|
||||
const { value: optionsToLatest, error: optionsError } = transforms.update.in.options.up<
|
||||
CreateOptions,
|
||||
CreateOptions
|
||||
>(options);
|
||||
if (optionsError) {
|
||||
throw Boom.badRequest(`Invalid options. ${optionsError.message}`);
|
||||
}
|
||||
|
||||
// Save data in DB
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
const partialSavedObject = await soClient.update<EventAnnotationGroupSavedObjectAttributes>(
|
||||
SO_TYPE,
|
||||
id,
|
||||
dataToLatest,
|
||||
optionsToLatest
|
||||
);
|
||||
|
||||
// Validate DB response and DOWN transform to the request version
|
||||
const { value, error: resultError } = transforms.update.out.result.down<
|
||||
EventAnnotationGroupUpdateOut,
|
||||
EventAnnotationGroupUpdateOut
|
||||
>({
|
||||
item: savedObjectToEventAnnotationGroupSavedObject(partialSavedObject, true),
|
||||
});
|
||||
|
||||
if (resultError) {
|
||||
throw Boom.badRequest(`Invalid response. ${resultError.message}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
async delete(ctx: StorageContext, id: string): Promise<EventAnnotationGroupDeleteOut> {
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
await soClient.delete(SO_TYPE, id);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async search(
|
||||
ctx: StorageContext,
|
||||
query: SearchQuery,
|
||||
options: EventAnnotationGroupSearchQuery = {}
|
||||
): Promise<EventAnnotationGroupSearchOut> {
|
||||
const {
|
||||
utils: { getTransforms },
|
||||
version: { request: requestVersion },
|
||||
} = ctx;
|
||||
const transforms = getTransforms(cmServicesDefinition, requestVersion);
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
|
||||
// Validate and UP transform the options
|
||||
const { value: optionsToLatest, error: optionsError } = transforms.search.in.options.up<
|
||||
EventAnnotationGroupSearchQuery,
|
||||
EventAnnotationGroupSearchQuery
|
||||
>(options);
|
||||
|
||||
if (optionsError) {
|
||||
throw Boom.badRequest(`Invalid payload. ${optionsError.message}`);
|
||||
}
|
||||
|
||||
const { searchFields = ['title^3', 'description'], types = [SO_TYPE] } = optionsToLatest;
|
||||
|
||||
const { included, excluded } = query.tags ?? {};
|
||||
const hasReference: SavedObjectsFindOptions['hasReference'] = included
|
||||
? included.map((id) => ({
|
||||
id,
|
||||
type: 'tag',
|
||||
}))
|
||||
: undefined;
|
||||
|
||||
const hasNoReference: SavedObjectsFindOptions['hasNoReference'] = excluded
|
||||
? excluded.map((id) => ({
|
||||
id,
|
||||
type: 'tag',
|
||||
}))
|
||||
: undefined;
|
||||
|
||||
const soQuery: SavedObjectsFindOptions = {
|
||||
type: types,
|
||||
search: query.text,
|
||||
perPage: query.limit,
|
||||
page: query.cursor ? Number(query.cursor) : undefined,
|
||||
defaultSearchOperator: 'AND',
|
||||
searchFields,
|
||||
hasReference,
|
||||
hasNoReference,
|
||||
};
|
||||
|
||||
// Execute the query in the DB
|
||||
const response = await soClient.find<EventAnnotationGroupSavedObjectAttributes>(soQuery);
|
||||
|
||||
// Validate the response and DOWN transform to the request version
|
||||
const { value, error: resultError } = transforms.search.out.result.down<
|
||||
EventAnnotationGroupSearchOut,
|
||||
EventAnnotationGroupSearchOut
|
||||
>({
|
||||
hits: response.saved_objects.map((so) =>
|
||||
savedObjectToEventAnnotationGroupSavedObject(so, false)
|
||||
),
|
||||
pagination: {
|
||||
total: response.total,
|
||||
},
|
||||
});
|
||||
|
||||
if (resultError) {
|
||||
throw Boom.badRequest(`Invalid response. ${resultError.message}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,5 +6,8 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { PluginInitializerContext } from '@kbn/core-plugins-server';
|
||||
import { EventAnnotationServerPlugin } from './plugin';
|
||||
export const plugin = () => new EventAnnotationServerPlugin();
|
||||
|
||||
export const plugin = (initializerContext: PluginInitializerContext) =>
|
||||
new EventAnnotationServerPlugin(initializerContext);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { CoreSetup, Plugin } from '@kbn/core/server';
|
||||
import { CoreSetup, Plugin, PluginInitializerContext } from '@kbn/core/server';
|
||||
import { ExpressionsServerSetup } from '@kbn/expressions-plugin/server';
|
||||
import { PluginStart as DataPluginStart } from '@kbn/data-plugin/server';
|
||||
import { ContentManagementServerSetup } from '@kbn/content-management-plugin/server';
|
||||
|
@ -29,6 +29,8 @@ export interface EventAnnotationStartDependencies {
|
|||
}
|
||||
|
||||
export class EventAnnotationServerPlugin implements Plugin<object, object> {
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {}
|
||||
|
||||
public setup(
|
||||
core: CoreSetup<EventAnnotationStartDependencies, object>,
|
||||
dependencies: SetupDependencies
|
||||
|
@ -42,7 +44,10 @@ export class EventAnnotationServerPlugin implements Plugin<object, object> {
|
|||
|
||||
dependencies.contentManagement.register({
|
||||
id: CONTENT_ID,
|
||||
storage: new EventAnnotationGroupStorage(),
|
||||
storage: new EventAnnotationGroupStorage({
|
||||
throwOnResultValidationError: this.initializerContext.env.mode.dev,
|
||||
logger: this.initializerContext.logger.get(),
|
||||
}),
|
||||
version: {
|
||||
latest: LATEST_VERSION,
|
||||
},
|
||||
|
|
|
@ -31,10 +31,11 @@
|
|||
"@kbn/object-versioning",
|
||||
"@kbn/config-schema",
|
||||
"@kbn/content-management-plugin",
|
||||
"@kbn/core-saved-objects-api-server",
|
||||
"@kbn/event-annotation-components",
|
||||
"@kbn/event-annotation-common",
|
||||
"@kbn/content-management-utils"
|
||||
"@kbn/content-management-utils",
|
||||
"@kbn/logging",
|
||||
"@kbn/core-plugins-server"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -27,6 +27,7 @@ export type {
|
|||
VisualizationSearchIn,
|
||||
VisualizationSearchOut,
|
||||
VisualizationSearchQuery,
|
||||
VisualizationCrudTypes,
|
||||
} from './latest';
|
||||
|
||||
export * as VisualizationV1 from './v1';
|
||||
|
|
|
@ -23,5 +23,6 @@ export type {
|
|||
VisualizationSearchIn,
|
||||
VisualizationSearchOut,
|
||||
VisualizationSearchQuery,
|
||||
VisualizationCrudTypes,
|
||||
Reference,
|
||||
} from './types';
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
CreateResult,
|
||||
UpdateResult,
|
||||
} from '@kbn/content-management-plugin/common';
|
||||
import { ContentManagementCrudTypes } from '@kbn/content-management-utils';
|
||||
|
||||
import { VisualizationContentType } from '../types';
|
||||
|
||||
|
@ -127,3 +128,13 @@ export interface VisualizationSearchQuery {
|
|||
export type VisualizationSearchIn = SearchIn<VisualizationContentType, {}>;
|
||||
|
||||
export type VisualizationSearchOut = SearchResult<VisualizationSavedObject>;
|
||||
|
||||
// ----------- CRUD TYPES --------------
|
||||
|
||||
export type VisualizationCrudTypes = ContentManagementCrudTypes<
|
||||
VisualizationContentType,
|
||||
VisualizationSavedObjectAttributes,
|
||||
CreateOptions,
|
||||
UpdateOptions,
|
||||
{}
|
||||
>;
|
||||
|
|
|
@ -5,343 +5,41 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import Boom from '@hapi/boom';
|
||||
import type { SearchQuery } from '@kbn/content-management-plugin/common';
|
||||
import type { ContentStorage, StorageContext } from '@kbn/content-management-plugin/server';
|
||||
import type {
|
||||
SavedObject,
|
||||
SavedObjectReference,
|
||||
SavedObjectsFindOptions,
|
||||
} from '@kbn/core-saved-objects-api-server';
|
||||
|
||||
import { getMSearch, type GetMSearchType } from '@kbn/content-management-utils';
|
||||
import { SOContentStorage } from '@kbn/content-management-utils';
|
||||
|
||||
import { CONTENT_ID } from '../../common/content_management';
|
||||
import { Logger } from '@kbn/logging';
|
||||
import { cmServicesDefinition } from '../../common/content_management/cm_services';
|
||||
import type {
|
||||
VisualizationSavedObjectAttributes,
|
||||
VisualizationSavedObject,
|
||||
PartialVisualizationSavedObject,
|
||||
VisualizationContentType,
|
||||
VisualizationGetOut,
|
||||
VisualizationCreateIn,
|
||||
VisualizationCreateOut,
|
||||
CreateOptions,
|
||||
VisualizationUpdateIn,
|
||||
VisualizationUpdateOut,
|
||||
UpdateOptions,
|
||||
VisualizationDeleteOut,
|
||||
VisualizationSearchQuery,
|
||||
VisualizationSearchOut,
|
||||
VisualizationCrudTypes,
|
||||
} from '../../common/content_management';
|
||||
|
||||
const savedObjectClientFromRequest = async (ctx: StorageContext) => {
|
||||
if (!ctx.requestHandlerContext) {
|
||||
throw new Error('Storage context.requestHandlerContext missing.');
|
||||
}
|
||||
|
||||
const { savedObjects } = await ctx.requestHandlerContext.core;
|
||||
return savedObjects.client;
|
||||
};
|
||||
|
||||
type PartialSavedObject<T> = Omit<SavedObject<Partial<T>>, 'references'> & {
|
||||
references: SavedObjectReference[] | undefined;
|
||||
};
|
||||
|
||||
function savedObjectToVisualizationSavedObject(
|
||||
savedObject: SavedObject<VisualizationSavedObjectAttributes>,
|
||||
partial: false
|
||||
): VisualizationSavedObject;
|
||||
|
||||
function savedObjectToVisualizationSavedObject(
|
||||
savedObject: PartialSavedObject<VisualizationSavedObjectAttributes>,
|
||||
partial: true
|
||||
): PartialVisualizationSavedObject;
|
||||
|
||||
function savedObjectToVisualizationSavedObject(
|
||||
savedObject:
|
||||
| SavedObject<VisualizationSavedObjectAttributes>
|
||||
| PartialSavedObject<VisualizationSavedObjectAttributes>
|
||||
): VisualizationSavedObject | PartialVisualizationSavedObject {
|
||||
const {
|
||||
id,
|
||||
type,
|
||||
updated_at: updatedAt,
|
||||
created_at: createdAt,
|
||||
attributes: {
|
||||
title,
|
||||
description,
|
||||
visState,
|
||||
kibanaSavedObjectMeta,
|
||||
uiStateJSON,
|
||||
savedSearchRefName,
|
||||
},
|
||||
references,
|
||||
error,
|
||||
namespaces,
|
||||
} = savedObject;
|
||||
|
||||
return {
|
||||
id,
|
||||
type,
|
||||
updatedAt,
|
||||
createdAt,
|
||||
attributes: {
|
||||
title,
|
||||
description,
|
||||
visState,
|
||||
kibanaSavedObjectMeta,
|
||||
uiStateJSON,
|
||||
savedSearchRefName,
|
||||
},
|
||||
references,
|
||||
error,
|
||||
namespaces,
|
||||
};
|
||||
}
|
||||
|
||||
const SO_TYPE: VisualizationContentType = 'visualization';
|
||||
|
||||
export class VisualizationsStorage
|
||||
implements ContentStorage<VisualizationSavedObject, PartialVisualizationSavedObject>
|
||||
{
|
||||
mSearch: GetMSearchType<VisualizationSavedObject>;
|
||||
|
||||
constructor() {
|
||||
this.mSearch = getMSearch<VisualizationSavedObject, VisualizationSearchOut>({
|
||||
export class VisualizationsStorage extends SOContentStorage<VisualizationCrudTypes> {
|
||||
constructor({
|
||||
logger,
|
||||
throwOnResultValidationError,
|
||||
}: {
|
||||
logger: Logger;
|
||||
throwOnResultValidationError: boolean;
|
||||
}) {
|
||||
super({
|
||||
savedObjectType: SO_TYPE,
|
||||
cmServicesDefinition,
|
||||
enableMSearch: true,
|
||||
allowedSavedObjectAttributes: [
|
||||
'title',
|
||||
'description',
|
||||
'version',
|
||||
'visState',
|
||||
'kibanaSavedObjectMeta',
|
||||
'uiStateJSON',
|
||||
'visState',
|
||||
'savedSearchRefName',
|
||||
],
|
||||
logger,
|
||||
throwOnResultValidationError,
|
||||
});
|
||||
}
|
||||
|
||||
async get(ctx: StorageContext, id: string): Promise<VisualizationGetOut> {
|
||||
const {
|
||||
utils: { getTransforms },
|
||||
version: { request: requestVersion },
|
||||
} = ctx;
|
||||
const transforms = getTransforms(cmServicesDefinition, requestVersion);
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
|
||||
// Save data in DB
|
||||
const {
|
||||
saved_object: savedObject,
|
||||
alias_purpose: aliasPurpose,
|
||||
alias_target_id: aliasTargetId,
|
||||
outcome,
|
||||
} = await soClient.resolve<VisualizationSavedObjectAttributes>(SO_TYPE, id);
|
||||
|
||||
const response: VisualizationGetOut = {
|
||||
item: savedObjectToVisualizationSavedObject(savedObject, false),
|
||||
meta: {
|
||||
aliasPurpose,
|
||||
aliasTargetId,
|
||||
outcome,
|
||||
},
|
||||
};
|
||||
|
||||
// Validate DB response and DOWN transform to the request version
|
||||
const { value, error: resultError } = transforms.get.out.result.down<
|
||||
VisualizationGetOut,
|
||||
VisualizationGetOut
|
||||
>(response);
|
||||
|
||||
if (resultError) {
|
||||
throw Boom.badRequest(`Invalid response. ${resultError.message}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
async bulkGet(): Promise<never> {
|
||||
// Not implemented. Visualizations does not use bulkGet
|
||||
throw new Error(`[bulkGet] has not been implemented. See VisualizationsStorage class.`);
|
||||
}
|
||||
|
||||
async create(
|
||||
ctx: StorageContext,
|
||||
data: VisualizationCreateIn['data'],
|
||||
options: CreateOptions
|
||||
): Promise<VisualizationCreateOut> {
|
||||
const {
|
||||
utils: { getTransforms },
|
||||
version: { request: requestVersion },
|
||||
} = ctx;
|
||||
const transforms = getTransforms(cmServicesDefinition, requestVersion);
|
||||
|
||||
// Validate input (data & options) & UP transform them to the latest version
|
||||
const { value: dataToLatest, error: dataError } = transforms.create.in.data.up<
|
||||
VisualizationSavedObjectAttributes,
|
||||
VisualizationSavedObjectAttributes
|
||||
>(data);
|
||||
if (dataError) {
|
||||
throw Boom.badRequest(`Invalid data. ${dataError.message}`);
|
||||
}
|
||||
|
||||
const { value: optionsToLatest, error: optionsError } = transforms.create.in.options.up<
|
||||
CreateOptions,
|
||||
CreateOptions
|
||||
>(options);
|
||||
if (optionsError) {
|
||||
throw Boom.badRequest(`Invalid options. ${optionsError.message}`);
|
||||
}
|
||||
|
||||
// Save data in DB
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
const savedObject = await soClient.create<VisualizationSavedObjectAttributes>(
|
||||
SO_TYPE,
|
||||
dataToLatest,
|
||||
optionsToLatest
|
||||
);
|
||||
|
||||
// Validate DB response and DOWN transform to the request version
|
||||
const { value, error: resultError } = transforms.create.out.result.down<
|
||||
VisualizationCreateOut,
|
||||
VisualizationCreateOut
|
||||
>({
|
||||
item: savedObjectToVisualizationSavedObject(savedObject, false),
|
||||
});
|
||||
|
||||
if (resultError) {
|
||||
throw Boom.badRequest(`Invalid response. ${resultError.message}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
async update(
|
||||
ctx: StorageContext,
|
||||
id: string,
|
||||
data: VisualizationUpdateIn['data'],
|
||||
options: UpdateOptions
|
||||
): Promise<VisualizationUpdateOut> {
|
||||
const {
|
||||
utils: { getTransforms },
|
||||
version: { request: requestVersion },
|
||||
} = ctx;
|
||||
const transforms = getTransforms(cmServicesDefinition, requestVersion);
|
||||
|
||||
// Validate input (data & options) & UP transform them to the latest version
|
||||
const { value: dataToLatest, error: dataError } = transforms.update.in.data.up<
|
||||
VisualizationSavedObjectAttributes,
|
||||
VisualizationSavedObjectAttributes
|
||||
>(data);
|
||||
if (dataError) {
|
||||
throw Boom.badRequest(`Invalid data. ${dataError.message}`);
|
||||
}
|
||||
|
||||
const { value: optionsToLatest, error: optionsError } = transforms.update.in.options.up<
|
||||
CreateOptions,
|
||||
CreateOptions
|
||||
>(options);
|
||||
if (optionsError) {
|
||||
throw Boom.badRequest(`Invalid options. ${optionsError.message}`);
|
||||
}
|
||||
|
||||
// Save data in DB
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
const partialSavedObject = await soClient.update<VisualizationSavedObjectAttributes>(
|
||||
SO_TYPE,
|
||||
id,
|
||||
dataToLatest,
|
||||
optionsToLatest
|
||||
);
|
||||
|
||||
// Validate DB response and DOWN transform to the request version
|
||||
const { value, error: resultError } = transforms.update.out.result.down<
|
||||
VisualizationUpdateOut,
|
||||
VisualizationUpdateOut
|
||||
>({
|
||||
item: savedObjectToVisualizationSavedObject(partialSavedObject, true),
|
||||
});
|
||||
|
||||
if (resultError) {
|
||||
throw Boom.badRequest(`Invalid response. ${resultError.message}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
async delete(ctx: StorageContext, id: string): Promise<VisualizationDeleteOut> {
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
await soClient.delete(SO_TYPE, id);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async search(
|
||||
ctx: StorageContext,
|
||||
query: SearchQuery,
|
||||
options: VisualizationSearchQuery = {}
|
||||
): Promise<VisualizationSearchOut> {
|
||||
const {
|
||||
utils: { getTransforms },
|
||||
version: { request: requestVersion },
|
||||
} = ctx;
|
||||
const transforms = getTransforms(cmServicesDefinition, requestVersion);
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
|
||||
// Validate and UP transform the options
|
||||
const { value: optionsToLatest, error: optionsError } = transforms.search.in.options.up<
|
||||
VisualizationSearchQuery,
|
||||
VisualizationSearchQuery
|
||||
>(options);
|
||||
if (optionsError) {
|
||||
throw Boom.badRequest(`Invalid payload. ${optionsError.message}`);
|
||||
}
|
||||
const { searchFields = ['title^3', 'description'], types = [CONTENT_ID] } = optionsToLatest;
|
||||
|
||||
const { included, excluded } = query.tags ?? {};
|
||||
const hasReference: SavedObjectsFindOptions['hasReference'] = included
|
||||
? included.map((id) => ({
|
||||
id,
|
||||
type: 'tag',
|
||||
}))
|
||||
: undefined;
|
||||
|
||||
const hasNoReference: SavedObjectsFindOptions['hasNoReference'] = excluded
|
||||
? excluded.map((id) => ({
|
||||
id,
|
||||
type: 'tag',
|
||||
}))
|
||||
: undefined;
|
||||
|
||||
const soQuery: SavedObjectsFindOptions = {
|
||||
type: types,
|
||||
search: query.text,
|
||||
perPage: query.limit,
|
||||
page: query.cursor ? +query.cursor : undefined,
|
||||
defaultSearchOperator: 'AND',
|
||||
searchFields,
|
||||
hasReference,
|
||||
hasNoReference,
|
||||
};
|
||||
|
||||
// Execute the query in the DB
|
||||
const response = await soClient.find<VisualizationSavedObjectAttributes>(soQuery);
|
||||
|
||||
// Validate the response and DOWN transform to the request version
|
||||
const { value, error: resultError } = transforms.search.out.result.down<
|
||||
VisualizationSearchOut,
|
||||
VisualizationSearchOut
|
||||
>({
|
||||
hits: response.saved_objects.map((so) => savedObjectToVisualizationSavedObject(so, false)),
|
||||
pagination: {
|
||||
total: response.total,
|
||||
},
|
||||
});
|
||||
|
||||
if (resultError) {
|
||||
throw Boom.badRequest(`Invalid response. ${resultError.message}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ export class VisualizationsPlugin
|
|||
{
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext) {
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {
|
||||
this.logger = initializerContext.logger.get();
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,10 @@ export class VisualizationsPlugin
|
|||
|
||||
plugins.contentManagement.register({
|
||||
id: CONTENT_ID,
|
||||
storage: new VisualizationsStorage(),
|
||||
storage: new VisualizationsStorage({
|
||||
logger: this.logger,
|
||||
throwOnResultValidationError: this.initializerContext.env.mode.dev,
|
||||
}),
|
||||
version: {
|
||||
latest: LATEST_VERSION,
|
||||
},
|
||||
|
|
|
@ -54,7 +54,6 @@
|
|||
"@kbn/saved-objects-management-plugin",
|
||||
"@kbn/saved-objects-finder-plugin",
|
||||
"@kbn/content-management-plugin",
|
||||
"@kbn/core-saved-objects-api-server",
|
||||
"@kbn/object-versioning",
|
||||
"@kbn/core-saved-objects-server",
|
||||
"@kbn/core-saved-objects-utils-server",
|
||||
|
@ -64,7 +63,8 @@
|
|||
"@kbn/content-management-utils",
|
||||
"@kbn/serverless",
|
||||
"@kbn/no-data-page-plugin",
|
||||
"@kbn/search-response-warnings"
|
||||
"@kbn/search-response-warnings",
|
||||
"@kbn/logging"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -26,6 +26,7 @@ export type {
|
|||
GraphSearchIn,
|
||||
GraphSearchOut,
|
||||
GraphSearchQuery,
|
||||
GraphCrudTypes,
|
||||
} from './latest';
|
||||
|
||||
export * as GraphV1 from './v1';
|
||||
|
|
|
@ -22,5 +22,6 @@ export type {
|
|||
GraphSearchIn,
|
||||
GraphSearchOut,
|
||||
GraphSearchQuery,
|
||||
GraphCrudTypes,
|
||||
Reference,
|
||||
} from './types';
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
CreateResult,
|
||||
UpdateResult,
|
||||
} from '@kbn/content-management-plugin/common';
|
||||
import { ContentManagementCrudTypes } from '@kbn/content-management-utils';
|
||||
|
||||
import { GraphContentType } from '../types';
|
||||
|
||||
|
@ -113,3 +114,13 @@ export interface GraphSearchQuery {
|
|||
export type GraphSearchIn = SearchIn<GraphContentType, {}>;
|
||||
|
||||
export type GraphSearchOut = SearchResult<GraphSavedObject>;
|
||||
|
||||
// ----------- CRUD TYPES --------------
|
||||
|
||||
export type GraphCrudTypes = ContentManagementCrudTypes<
|
||||
GraphContentType,
|
||||
GraphSavedObjectAttributes,
|
||||
CreateOptions,
|
||||
UpdateOptions,
|
||||
{}
|
||||
>;
|
||||
|
|
|
@ -4,325 +4,37 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import Boom from '@hapi/boom';
|
||||
import type { SearchQuery } from '@kbn/content-management-plugin/common';
|
||||
import type { ContentStorage, StorageContext } from '@kbn/content-management-plugin/server';
|
||||
import type {
|
||||
SavedObject,
|
||||
SavedObjectReference,
|
||||
SavedObjectsFindOptions,
|
||||
} from '@kbn/core-saved-objects-api-server';
|
||||
|
||||
import { Logger } from '@kbn/logging';
|
||||
import { SOContentStorage } from '@kbn/content-management-utils';
|
||||
import { cmServicesDefinition } from '../../common/content_management/cm_services';
|
||||
import type {
|
||||
GraphSavedObjectAttributes,
|
||||
GraphSavedObject,
|
||||
PartialGraphSavedObject,
|
||||
GraphGetOut,
|
||||
GraphCreateIn,
|
||||
GraphCreateOut,
|
||||
CreateOptions,
|
||||
GraphUpdateIn,
|
||||
GraphUpdateOut,
|
||||
UpdateOptions,
|
||||
GraphDeleteOut,
|
||||
GraphSearchQuery,
|
||||
GraphSearchOut,
|
||||
} from '../../common/content_management';
|
||||
|
||||
const savedObjectClientFromRequest = async (ctx: StorageContext) => {
|
||||
if (!ctx.requestHandlerContext) {
|
||||
throw new Error('Storage context.requestHandlerContext missing.');
|
||||
}
|
||||
|
||||
const { savedObjects } = await ctx.requestHandlerContext.core;
|
||||
return savedObjects.client;
|
||||
};
|
||||
|
||||
type PartialSavedObject<T> = Omit<SavedObject<Partial<T>>, 'references'> & {
|
||||
references: SavedObjectReference[] | undefined;
|
||||
};
|
||||
|
||||
function savedObjectToGraphSavedObject(
|
||||
savedObject: SavedObject<GraphSavedObjectAttributes>,
|
||||
partial: false
|
||||
): GraphSavedObject;
|
||||
|
||||
function savedObjectToGraphSavedObject(
|
||||
savedObject: PartialSavedObject<GraphSavedObjectAttributes>,
|
||||
partial: true
|
||||
): PartialGraphSavedObject;
|
||||
|
||||
function savedObjectToGraphSavedObject(
|
||||
savedObject:
|
||||
| SavedObject<GraphSavedObjectAttributes>
|
||||
| PartialSavedObject<GraphSavedObjectAttributes>
|
||||
): GraphSavedObject | PartialGraphSavedObject {
|
||||
const {
|
||||
id,
|
||||
type,
|
||||
updated_at: updatedAt,
|
||||
created_at: createdAt,
|
||||
attributes: {
|
||||
title,
|
||||
description,
|
||||
version,
|
||||
kibanaSavedObjectMeta,
|
||||
wsState,
|
||||
numVertices,
|
||||
numLinks,
|
||||
legacyIndexPatternRef,
|
||||
},
|
||||
references,
|
||||
error,
|
||||
namespaces,
|
||||
} = savedObject;
|
||||
|
||||
return {
|
||||
id,
|
||||
type,
|
||||
updatedAt,
|
||||
createdAt,
|
||||
attributes: {
|
||||
title,
|
||||
description,
|
||||
kibanaSavedObjectMeta,
|
||||
wsState,
|
||||
version,
|
||||
numLinks,
|
||||
numVertices,
|
||||
legacyIndexPatternRef,
|
||||
},
|
||||
references,
|
||||
error,
|
||||
namespaces,
|
||||
};
|
||||
}
|
||||
import type { GraphCrudTypes } from '../../common/content_management';
|
||||
|
||||
const SO_TYPE = 'graph-workspace';
|
||||
|
||||
export class GraphStorage implements ContentStorage<GraphSavedObject, PartialGraphSavedObject> {
|
||||
constructor() {}
|
||||
|
||||
async get(ctx: StorageContext, id: string): Promise<GraphGetOut> {
|
||||
const {
|
||||
utils: { getTransforms },
|
||||
version: { request: requestVersion },
|
||||
} = ctx;
|
||||
const transforms = getTransforms(cmServicesDefinition, requestVersion);
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
|
||||
// Save data in DB
|
||||
const {
|
||||
saved_object: savedObject,
|
||||
alias_purpose: aliasPurpose,
|
||||
alias_target_id: aliasTargetId,
|
||||
outcome,
|
||||
} = await soClient.resolve<GraphSavedObjectAttributes>(SO_TYPE, id);
|
||||
|
||||
const response: GraphGetOut = {
|
||||
item: savedObjectToGraphSavedObject(savedObject, false),
|
||||
meta: {
|
||||
aliasPurpose,
|
||||
aliasTargetId,
|
||||
outcome,
|
||||
},
|
||||
};
|
||||
|
||||
// Validate DB response and DOWN transform to the request version
|
||||
const { value, error: resultError } = transforms.get.out.result.down<GraphGetOut, GraphGetOut>(
|
||||
response
|
||||
);
|
||||
|
||||
if (resultError) {
|
||||
throw Boom.badRequest(`Invalid response. ${resultError.message}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
async bulkGet(): Promise<never> {
|
||||
// Not implemented. Graph does not use bulkGet
|
||||
throw new Error(`[bulkGet] has not been implemented. See GraphStorage class.`);
|
||||
}
|
||||
|
||||
async create(
|
||||
ctx: StorageContext,
|
||||
data: GraphCreateIn['data'],
|
||||
options: CreateOptions
|
||||
): Promise<GraphCreateOut> {
|
||||
const {
|
||||
utils: { getTransforms },
|
||||
version: { request: requestVersion },
|
||||
} = ctx;
|
||||
const transforms = getTransforms(cmServicesDefinition, requestVersion);
|
||||
|
||||
// Validate input (data & options) & UP transform them to the latest version
|
||||
const { value: dataToLatest, error: dataError } = transforms.create.in.data.up<
|
||||
GraphSavedObjectAttributes,
|
||||
GraphSavedObjectAttributes
|
||||
>(data);
|
||||
if (dataError) {
|
||||
throw Boom.badRequest(`Invalid data. ${dataError.message}`);
|
||||
}
|
||||
|
||||
const { value: optionsToLatest, error: optionsError } = transforms.create.in.options.up<
|
||||
CreateOptions,
|
||||
CreateOptions
|
||||
>(options);
|
||||
if (optionsError) {
|
||||
throw Boom.badRequest(`Invalid options. ${optionsError.message}`);
|
||||
}
|
||||
|
||||
// Save data in DB
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
const savedObject = await soClient.create<GraphSavedObjectAttributes>(
|
||||
SO_TYPE,
|
||||
dataToLatest,
|
||||
optionsToLatest
|
||||
);
|
||||
|
||||
// Validate DB response and DOWN transform to the request version
|
||||
const { value, error: resultError } = transforms.create.out.result.down<
|
||||
GraphCreateOut,
|
||||
GraphCreateOut
|
||||
>({
|
||||
item: savedObjectToGraphSavedObject(savedObject, false),
|
||||
export class GraphStorage extends SOContentStorage<GraphCrudTypes> {
|
||||
constructor({
|
||||
logger,
|
||||
throwOnResultValidationError,
|
||||
}: {
|
||||
logger: Logger;
|
||||
throwOnResultValidationError: boolean;
|
||||
}) {
|
||||
super({
|
||||
savedObjectType: SO_TYPE,
|
||||
cmServicesDefinition,
|
||||
allowedSavedObjectAttributes: [
|
||||
'title',
|
||||
'description',
|
||||
'kibanaSavedObjectMeta',
|
||||
'wsState',
|
||||
'version',
|
||||
'numLinks',
|
||||
'numVertices',
|
||||
'legacyIndexPatternRef',
|
||||
],
|
||||
logger,
|
||||
throwOnResultValidationError,
|
||||
});
|
||||
|
||||
if (resultError) {
|
||||
throw Boom.badRequest(`Invalid response. ${resultError.message}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
async update(
|
||||
ctx: StorageContext,
|
||||
id: string,
|
||||
data: GraphUpdateIn['data'],
|
||||
options: UpdateOptions
|
||||
): Promise<GraphUpdateOut> {
|
||||
const {
|
||||
utils: { getTransforms },
|
||||
version: { request: requestVersion },
|
||||
} = ctx;
|
||||
const transforms = getTransforms(cmServicesDefinition, requestVersion);
|
||||
|
||||
// Validate input (data & options) & UP transform them to the latest version
|
||||
const { value: dataToLatest, error: dataError } = transforms.update.in.data.up<
|
||||
GraphSavedObjectAttributes,
|
||||
GraphSavedObjectAttributes
|
||||
>(data);
|
||||
if (dataError) {
|
||||
throw Boom.badRequest(`Invalid data. ${dataError.message}`);
|
||||
}
|
||||
|
||||
const { value: optionsToLatest, error: optionsError } = transforms.update.in.options.up<
|
||||
CreateOptions,
|
||||
CreateOptions
|
||||
>(options);
|
||||
if (optionsError) {
|
||||
throw Boom.badRequest(`Invalid options. ${optionsError.message}`);
|
||||
}
|
||||
|
||||
// Save data in DB
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
const partialSavedObject = await soClient.update<GraphSavedObjectAttributes>(
|
||||
SO_TYPE,
|
||||
id,
|
||||
dataToLatest,
|
||||
optionsToLatest
|
||||
);
|
||||
|
||||
// Validate DB response and DOWN transform to the request version
|
||||
const { value, error: resultError } = transforms.update.out.result.down<
|
||||
GraphUpdateOut,
|
||||
GraphUpdateOut
|
||||
>({
|
||||
item: savedObjectToGraphSavedObject(partialSavedObject, true),
|
||||
});
|
||||
|
||||
if (resultError) {
|
||||
throw Boom.badRequest(`Invalid response. ${resultError.message}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
async delete(ctx: StorageContext, id: string): Promise<GraphDeleteOut> {
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
await soClient.delete(SO_TYPE, id);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async search(
|
||||
ctx: StorageContext,
|
||||
query: SearchQuery,
|
||||
options: GraphSearchQuery = {}
|
||||
): Promise<GraphSearchOut> {
|
||||
const {
|
||||
utils: { getTransforms },
|
||||
version: { request: requestVersion },
|
||||
} = ctx;
|
||||
const transforms = getTransforms(cmServicesDefinition, requestVersion);
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
|
||||
// Validate and UP transform the options
|
||||
const { value: optionsToLatest, error: optionsError } = transforms.search.in.options.up<
|
||||
GraphSearchQuery,
|
||||
GraphSearchQuery
|
||||
>(options);
|
||||
if (optionsError) {
|
||||
throw Boom.badRequest(`Invalid payload. ${optionsError.message}`);
|
||||
}
|
||||
const { searchFields = ['title^3', 'description'], types = ['graph-workspace'] } =
|
||||
optionsToLatest;
|
||||
|
||||
const { included, excluded } = query.tags ?? {};
|
||||
const hasReference: SavedObjectsFindOptions['hasReference'] = included
|
||||
? included.map((id) => ({
|
||||
id,
|
||||
type: 'tag',
|
||||
}))
|
||||
: undefined;
|
||||
|
||||
const hasNoReference: SavedObjectsFindOptions['hasNoReference'] = excluded
|
||||
? excluded.map((id) => ({
|
||||
id,
|
||||
type: 'tag',
|
||||
}))
|
||||
: undefined;
|
||||
|
||||
const soQuery: SavedObjectsFindOptions = {
|
||||
type: types,
|
||||
search: query.text,
|
||||
perPage: query.limit,
|
||||
page: query.cursor ? Number(query.cursor) : undefined,
|
||||
defaultSearchOperator: 'AND',
|
||||
searchFields,
|
||||
hasReference,
|
||||
hasNoReference,
|
||||
};
|
||||
|
||||
// Execute the query in the DB
|
||||
const response = await soClient.find<GraphSavedObjectAttributes>(soQuery);
|
||||
|
||||
// Validate the response and DOWN transform to the request version
|
||||
const { value, error: resultError } = transforms.search.out.result.down<
|
||||
GraphSearchOut,
|
||||
GraphSearchOut
|
||||
>({
|
||||
hits: response.saved_objects.map((so) => savedObjectToGraphSavedObject(so, false)),
|
||||
pagination: {
|
||||
total: response.total,
|
||||
},
|
||||
});
|
||||
|
||||
if (resultError) {
|
||||
throw Boom.badRequest(`Invalid response. ${resultError.message}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { PluginConfigDescriptor } from '@kbn/core/server';
|
||||
import { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/server';
|
||||
|
||||
import { configSchema, ConfigSchema } from '../config';
|
||||
import { GraphPlugin } from './plugin';
|
||||
|
||||
export const plugin = () => new GraphPlugin();
|
||||
export const plugin = (initializerContext: PluginInitializerContext) =>
|
||||
new GraphPlugin(initializerContext);
|
||||
|
||||
export const config: PluginConfigDescriptor<ConfigSchema> = {
|
||||
exposeToBrowser: {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Plugin, CoreSetup, CoreStart } from '@kbn/core/server';
|
||||
import { Plugin, CoreSetup, CoreStart, PluginInitializerContext } from '@kbn/core/server';
|
||||
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
|
||||
import { LicensingPluginSetup, LicensingPluginStart } from '@kbn/licensing-plugin/server';
|
||||
import { HomeServerPluginSetup } from '@kbn/home-plugin/server';
|
||||
|
@ -23,6 +23,8 @@ import { GraphStorage } from './content_management/graph_storage';
|
|||
export class GraphPlugin implements Plugin {
|
||||
private licenseState: LicenseState | null = null;
|
||||
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {}
|
||||
|
||||
public setup(
|
||||
core: CoreSetup,
|
||||
{
|
||||
|
@ -45,7 +47,10 @@ export class GraphPlugin implements Plugin {
|
|||
|
||||
contentManagement.register({
|
||||
id: CONTENT_ID,
|
||||
storage: new GraphStorage(),
|
||||
storage: new GraphStorage({
|
||||
throwOnResultValidationError: this.initializerContext.env.mode.dev,
|
||||
logger: this.initializerContext.logger.get(),
|
||||
}),
|
||||
version: {
|
||||
latest: LATEST_VERSION,
|
||||
},
|
||||
|
|
|
@ -41,12 +41,13 @@
|
|||
"@kbn/saved-objects-finder-plugin",
|
||||
"@kbn/core-saved-objects-server",
|
||||
"@kbn/content-management-plugin",
|
||||
"@kbn/core-saved-objects-api-server",
|
||||
"@kbn/object-versioning",
|
||||
"@kbn/content-management-table-list-view-table",
|
||||
"@kbn/content-management-table-list-view",
|
||||
"@kbn/core-ui-settings-browser",
|
||||
"@kbn/react-kibana-mount",
|
||||
"@kbn/content-management-utils",
|
||||
"@kbn/logging",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue