mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
content management - event annotations (#159692)
This commit is contained in:
parent
5751f29f58
commit
b18d8d4c43
20 changed files with 899 additions and 84 deletions
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type {
|
||||
ContentManagementServicesDefinition as ServicesDefinition,
|
||||
Version,
|
||||
} from '@kbn/object-versioning';
|
||||
|
||||
// We export the versioned service definition from this file and not the barrel to avoid adding
|
||||
// the schemas in the "public" js bundle
|
||||
|
||||
import { serviceDefinition as v1 } from './v1/cm_services';
|
||||
|
||||
export const cmServicesDefinition: { [version: Version]: ServicesDefinition } = {
|
||||
1: v1,
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export const LATEST_VERSION = 1;
|
||||
|
||||
export const CONTENT_ID = 'event-annotation-group';
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { LATEST_VERSION, CONTENT_ID } from './constants';
|
||||
|
||||
export type { EventAnnotationGroupContentType } from './types';
|
||||
|
||||
export type {
|
||||
EventAnnotationGroupSavedObject,
|
||||
PartialEventAnnotationGroupSavedObject,
|
||||
EventAnnotationGroupSavedObjectAttributes,
|
||||
EventAnnotationGroupGetIn,
|
||||
EventAnnotationGroupGetOut,
|
||||
EventAnnotationGroupCreateIn,
|
||||
EventAnnotationGroupCreateOut,
|
||||
CreateOptions,
|
||||
EventAnnotationGroupUpdateIn,
|
||||
EventAnnotationGroupUpdateOut,
|
||||
UpdateOptions,
|
||||
EventAnnotationGroupDeleteIn,
|
||||
EventAnnotationGroupDeleteOut,
|
||||
EventAnnotationGroupSearchIn,
|
||||
EventAnnotationGroupSearchOut,
|
||||
EventAnnotationGroupSearchQuery,
|
||||
} from './latest';
|
||||
|
||||
export * as EventAnnotationGroupV1 from './v1';
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export * from './v1';
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export type EventAnnotationGroupContentType = 'event-annotation-group';
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { ContentManagementServicesDefinition as ServicesDefinition } from '@kbn/object-versioning';
|
||||
|
||||
const apiError = schema.object({
|
||||
error: schema.string(),
|
||||
message: schema.string(),
|
||||
statusCode: schema.number(),
|
||||
metadata: schema.object({}, { unknowns: 'allow' }),
|
||||
});
|
||||
|
||||
const referenceSchema = schema.object(
|
||||
{
|
||||
name: schema.maybe(schema.string()),
|
||||
type: schema.string(),
|
||||
id: schema.string(),
|
||||
},
|
||||
{ unknowns: 'forbid' }
|
||||
);
|
||||
|
||||
const referencesSchema = schema.arrayOf(referenceSchema);
|
||||
|
||||
const eventAnnotationGroupAttributesSchema = schema.object(
|
||||
{
|
||||
title: schema.string(),
|
||||
description: schema.maybe(schema.string()),
|
||||
ignoreGlobalFilters: schema.boolean(),
|
||||
annotations: schema.arrayOf(schema.any()),
|
||||
dataViewSpec: schema.maybe(schema.any()),
|
||||
},
|
||||
{ unknowns: 'forbid' }
|
||||
);
|
||||
|
||||
const eventAnnotationGroupSavedObjectSchema = schema.object(
|
||||
{
|
||||
id: schema.string(),
|
||||
type: schema.string(),
|
||||
version: schema.maybe(schema.string()),
|
||||
createdAt: schema.maybe(schema.string()),
|
||||
updatedAt: schema.maybe(schema.string()),
|
||||
error: schema.maybe(apiError),
|
||||
attributes: eventAnnotationGroupAttributesSchema,
|
||||
references: referencesSchema,
|
||||
namespaces: schema.maybe(schema.arrayOf(schema.string())),
|
||||
originId: schema.maybe(schema.string()),
|
||||
},
|
||||
{ unknowns: 'allow' }
|
||||
);
|
||||
|
||||
const getResultSchema = schema.object(
|
||||
{
|
||||
item: eventAnnotationGroupSavedObjectSchema,
|
||||
meta: schema.object(
|
||||
{
|
||||
outcome: schema.oneOf([
|
||||
schema.literal('exactMatch'),
|
||||
schema.literal('aliasMatch'),
|
||||
schema.literal('conflict'),
|
||||
]),
|
||||
aliasTargetId: schema.maybe(schema.string()),
|
||||
aliasPurpose: schema.maybe(
|
||||
schema.oneOf([
|
||||
schema.literal('savedObjectConversion'),
|
||||
schema.literal('savedObjectImport'),
|
||||
])
|
||||
),
|
||||
},
|
||||
{ unknowns: 'forbid' }
|
||||
),
|
||||
},
|
||||
{ unknowns: 'forbid' }
|
||||
);
|
||||
|
||||
const createOptionsSchema = schema.object({
|
||||
overwrite: schema.maybe(schema.boolean()),
|
||||
references: schema.maybe(referencesSchema),
|
||||
});
|
||||
|
||||
// Content management service definition.
|
||||
// We need it for BWC support between different versions of the content
|
||||
export const serviceDefinition: ServicesDefinition = {
|
||||
get: {
|
||||
out: {
|
||||
result: {
|
||||
schema: getResultSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
create: {
|
||||
in: {
|
||||
options: {
|
||||
schema: createOptionsSchema,
|
||||
},
|
||||
data: {
|
||||
schema: eventAnnotationGroupAttributesSchema,
|
||||
},
|
||||
},
|
||||
out: {
|
||||
result: {
|
||||
schema: schema.object(
|
||||
{
|
||||
item: eventAnnotationGroupSavedObjectSchema,
|
||||
},
|
||||
{ unknowns: 'forbid' }
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
update: {
|
||||
in: {
|
||||
options: {
|
||||
schema: createOptionsSchema, // same schema as "create"
|
||||
},
|
||||
data: {
|
||||
schema: eventAnnotationGroupAttributesSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
search: {
|
||||
in: {
|
||||
options: {
|
||||
schema: schema.maybe(
|
||||
schema.object(
|
||||
{
|
||||
searchFields: schema.maybe(schema.arrayOf(schema.string())),
|
||||
types: schema.maybe(schema.arrayOf(schema.string())),
|
||||
},
|
||||
{ unknowns: 'forbid' }
|
||||
)
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export type {
|
||||
EventAnnotationGroupSavedObject as EventAnnotationGroupSavedObject,
|
||||
PartialEventAnnotationGroupSavedObject,
|
||||
EventAnnotationGroupSavedObjectAttributes,
|
||||
EventAnnotationGroupGetIn,
|
||||
EventAnnotationGroupGetOut,
|
||||
EventAnnotationGroupCreateIn,
|
||||
EventAnnotationGroupCreateOut,
|
||||
CreateOptions,
|
||||
EventAnnotationGroupUpdateIn,
|
||||
EventAnnotationGroupUpdateOut,
|
||||
UpdateOptions,
|
||||
EventAnnotationGroupDeleteIn,
|
||||
EventAnnotationGroupDeleteOut,
|
||||
EventAnnotationGroupSearchIn,
|
||||
EventAnnotationGroupSearchOut,
|
||||
EventAnnotationGroupSearchQuery,
|
||||
Reference,
|
||||
} from './types';
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import {
|
||||
GetIn,
|
||||
CreateIn,
|
||||
SearchIn,
|
||||
UpdateIn,
|
||||
DeleteIn,
|
||||
DeleteResult,
|
||||
SearchResult,
|
||||
GetResult,
|
||||
CreateResult,
|
||||
UpdateResult,
|
||||
} from '@kbn/content-management-plugin/common';
|
||||
|
||||
import type { DataViewSpec } from '@kbn/data-views-plugin/common';
|
||||
import { EventAnnotationGroupContentType } from '../types';
|
||||
import { EventAnnotationConfig } from '../../types';
|
||||
|
||||
export interface Reference {
|
||||
type: string;
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface EventAnnotationGroupSavedObjectAttributes {
|
||||
title: string;
|
||||
description: string;
|
||||
ignoreGlobalFilters: boolean;
|
||||
annotations: EventAnnotationConfig[];
|
||||
dataViewSpec?: DataViewSpec;
|
||||
}
|
||||
|
||||
export interface EventAnnotationGroupSavedObject {
|
||||
id: string;
|
||||
type: string;
|
||||
version?: string;
|
||||
updatedAt?: string;
|
||||
createdAt?: string;
|
||||
attributes: EventAnnotationGroupSavedObjectAttributes;
|
||||
references: Reference[];
|
||||
namespaces?: string[];
|
||||
originId?: string;
|
||||
error?: {
|
||||
error: string;
|
||||
message: string;
|
||||
statusCode: number;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
}
|
||||
|
||||
export type PartialEventAnnotationGroupSavedObject = Omit<
|
||||
EventAnnotationGroupSavedObject,
|
||||
'attributes' | 'references'
|
||||
> & {
|
||||
attributes: Partial<EventAnnotationGroupSavedObjectAttributes>;
|
||||
references: Reference[] | undefined;
|
||||
};
|
||||
// ----------- GET --------------
|
||||
|
||||
export type EventAnnotationGroupGetIn = GetIn<EventAnnotationGroupContentType>;
|
||||
|
||||
export type EventAnnotationGroupGetOut = GetResult<
|
||||
EventAnnotationGroupSavedObject,
|
||||
{
|
||||
outcome: 'exactMatch' | 'aliasMatch' | 'conflict';
|
||||
aliasTargetId?: string;
|
||||
aliasPurpose?: 'savedObjectConversion' | 'savedObjectImport';
|
||||
}
|
||||
>;
|
||||
|
||||
// ----------- CREATE --------------
|
||||
|
||||
export interface CreateOptions {
|
||||
/** If a document with the given `id` already exists, overwrite it's contents (default=false). */
|
||||
overwrite?: boolean;
|
||||
/** Array of referenced saved objects. */
|
||||
references?: Reference[];
|
||||
}
|
||||
|
||||
export type EventAnnotationGroupCreateIn = CreateIn<
|
||||
EventAnnotationGroupContentType,
|
||||
EventAnnotationGroupSavedObjectAttributes,
|
||||
CreateOptions
|
||||
>;
|
||||
|
||||
export type EventAnnotationGroupCreateOut = CreateResult<EventAnnotationGroupSavedObject>;
|
||||
|
||||
// ----------- UPDATE --------------
|
||||
|
||||
export interface UpdateOptions {
|
||||
/** Array of referenced saved objects. */
|
||||
references?: Reference[];
|
||||
}
|
||||
|
||||
export type EventAnnotationGroupUpdateIn = UpdateIn<
|
||||
EventAnnotationGroupContentType,
|
||||
EventAnnotationGroupSavedObjectAttributes,
|
||||
UpdateOptions
|
||||
>;
|
||||
|
||||
export type EventAnnotationGroupUpdateOut = UpdateResult<PartialEventAnnotationGroupSavedObject>;
|
||||
|
||||
// ----------- DELETE --------------
|
||||
|
||||
export type EventAnnotationGroupDeleteIn = DeleteIn<EventAnnotationGroupContentType>;
|
||||
|
||||
export type EventAnnotationGroupDeleteOut = DeleteResult;
|
||||
|
||||
// ----------- SEARCH --------------
|
||||
|
||||
export interface EventAnnotationGroupSearchQuery {
|
||||
types?: string[];
|
||||
searchFields?: string[];
|
||||
}
|
||||
|
||||
export type EventAnnotationGroupSearchIn = SearchIn<EventAnnotationGroupContentType, {}>;
|
||||
|
||||
export type EventAnnotationGroupSearchOut = SearchResult<EventAnnotationGroupSavedObject>;
|
|
@ -44,4 +44,5 @@ export type {
|
|||
EventAnnotationGroupAttributes,
|
||||
} from './types';
|
||||
|
||||
export type { EventAnnotationGroupSavedObjectAttributes } from './content_management';
|
||||
export { EVENT_ANNOTATION_GROUP_TYPE, ANNOTATIONS_LISTING_VIEW_ID } from './constants';
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
"dataViews",
|
||||
"unifiedSearch",
|
||||
"kibanaUtils",
|
||||
"visualizationUiComponents"
|
||||
"visualizationUiComponents",
|
||||
"contentManagement"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"savedObjectsTagging",
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import { CoreStart } from '@kbn/core/public';
|
||||
import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
|
||||
import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
|
||||
import { EventAnnotationServiceType } from './types';
|
||||
|
||||
export class EventAnnotationService {
|
||||
|
@ -15,9 +16,15 @@ export class EventAnnotationService {
|
|||
|
||||
private core: CoreStart;
|
||||
private savedObjectsManagement: SavedObjectsManagementPluginStart;
|
||||
private contentManagement: ContentManagementPublicStart;
|
||||
|
||||
constructor(core: CoreStart, savedObjectsManagement: SavedObjectsManagementPluginStart) {
|
||||
constructor(
|
||||
core: CoreStart,
|
||||
contentManagement: ContentManagementPublicStart,
|
||||
savedObjectsManagement: SavedObjectsManagementPluginStart
|
||||
) {
|
||||
this.core = core;
|
||||
this.contentManagement = contentManagement;
|
||||
this.savedObjectsManagement = savedObjectsManagement;
|
||||
}
|
||||
|
||||
|
@ -26,6 +33,7 @@ export class EventAnnotationService {
|
|||
const { getEventAnnotationService } = await import('./service');
|
||||
this.eventAnnotationService = getEventAnnotationService(
|
||||
this.core,
|
||||
this.contentManagement,
|
||||
this.savedObjectsManagement
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SavedObjectsFindResponse } from '@kbn/core-saved-objects-api-browser';
|
||||
import { CoreStart, SimpleSavedObject } from '@kbn/core/public';
|
||||
import { ContentClient, ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
|
||||
import { EventAnnotationConfig, EventAnnotationGroupAttributes } from '../../common';
|
||||
|
@ -131,28 +131,35 @@ const annotationResolveMocks = {
|
|||
},
|
||||
};
|
||||
|
||||
const contentClient = {
|
||||
get: jest.fn(),
|
||||
search: jest.fn(),
|
||||
create: jest.fn(),
|
||||
update: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
} as unknown as ContentClient;
|
||||
|
||||
let core: CoreStart;
|
||||
|
||||
describe('Event Annotation Service', () => {
|
||||
let eventAnnotationService: EventAnnotationServiceType;
|
||||
beforeEach(() => {
|
||||
core = coreMock.createStart();
|
||||
(core.savedObjects.client.create as jest.Mock).mockImplementation(() => {
|
||||
return annotationGroupResolveMocks.multiAnnotations;
|
||||
(contentClient.create as jest.Mock).mockImplementation(() => {
|
||||
return { item: annotationGroupResolveMocks.multiAnnotations };
|
||||
});
|
||||
(core.savedObjects.client.get as jest.Mock).mockImplementation((_type, id) => {
|
||||
(contentClient.get as jest.Mock).mockImplementation(({ contentTypeId, id }) => {
|
||||
const typedId = id as keyof typeof annotationGroupResolveMocks;
|
||||
return annotationGroupResolveMocks[typedId];
|
||||
return { item: annotationGroupResolveMocks[typedId] };
|
||||
});
|
||||
(core.savedObjects.client.find as jest.Mock).mockResolvedValue({
|
||||
total: 10,
|
||||
savedObjects: Object.values(annotationGroupResolveMocks),
|
||||
} as Pick<SavedObjectsFindResponse<EventAnnotationGroupAttributes>, 'total' | 'savedObjects'>);
|
||||
(core.savedObjects.client.bulkCreate as jest.Mock).mockImplementation(() => {
|
||||
return annotationResolveMocks.multiAnnotations;
|
||||
(contentClient.search as jest.Mock).mockResolvedValue({
|
||||
pagination: { total: 10 },
|
||||
hits: Object.values(annotationGroupResolveMocks),
|
||||
});
|
||||
(contentClient.delete as jest.Mock).mockResolvedValue({});
|
||||
eventAnnotationService = getEventAnnotationService(
|
||||
core,
|
||||
{ client: contentClient } as ContentManagementPublicStart,
|
||||
{} as SavedObjectsManagementPluginStart
|
||||
);
|
||||
});
|
||||
|
@ -512,28 +519,14 @@ describe('Event Annotation Service', () => {
|
|||
|
||||
expect(content).toMatchSnapshot();
|
||||
|
||||
expect((core.savedObjects.client.find as jest.Mock).mock.calls).toMatchInlineSnapshot(`
|
||||
expect((contentClient.search as jest.Mock).mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"defaultSearchOperator": "AND",
|
||||
"hasNoReference": undefined,
|
||||
"hasReference": Array [
|
||||
Object {
|
||||
"id": "1234",
|
||||
"type": "mytype",
|
||||
},
|
||||
],
|
||||
"page": 1,
|
||||
"perPage": 20,
|
||||
"search": "my search*",
|
||||
"searchFields": Array [
|
||||
"title^3",
|
||||
"description",
|
||||
],
|
||||
"type": Array [
|
||||
"event-annotation-group",
|
||||
],
|
||||
"contentTypeId": "event-annotation-group",
|
||||
"query": Object {
|
||||
"text": "my search*",
|
||||
},
|
||||
},
|
||||
],
|
||||
]
|
||||
|
@ -543,10 +536,14 @@ describe('Event Annotation Service', () => {
|
|||
describe('deleteAnnotationGroups', () => {
|
||||
it('deletes annotation group along with annotations that reference them', async () => {
|
||||
await eventAnnotationService.deleteAnnotationGroups(['id1', 'id2']);
|
||||
expect(core.savedObjects.client.bulkDelete).toHaveBeenCalledWith([
|
||||
{ id: 'id1', type: 'event-annotation-group' },
|
||||
{ id: 'id2', type: 'event-annotation-group' },
|
||||
]);
|
||||
expect(contentClient.delete).toHaveBeenCalledWith({
|
||||
id: 'id1',
|
||||
contentTypeId: 'event-annotation-group',
|
||||
});
|
||||
expect(contentClient.delete).toHaveBeenCalledWith({
|
||||
id: 'id2',
|
||||
contentTypeId: 'event-annotation-group',
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('createAnnotationGroup', () => {
|
||||
|
@ -563,16 +560,16 @@ describe('Event Annotation Service', () => {
|
|||
ignoreGlobalFilters: false,
|
||||
annotations,
|
||||
});
|
||||
expect(core.savedObjects.client.create).toHaveBeenCalledWith(
|
||||
'event-annotation-group',
|
||||
{
|
||||
expect(contentClient.create).toHaveBeenCalledWith({
|
||||
contentTypeId: 'event-annotation-group',
|
||||
data: {
|
||||
title: 'newGroupTitle',
|
||||
description: 'my description',
|
||||
ignoreGlobalFilters: false,
|
||||
dataViewSpec: null,
|
||||
dataViewSpec: undefined,
|
||||
annotations,
|
||||
},
|
||||
{
|
||||
options: {
|
||||
references: [
|
||||
{
|
||||
id: 'ipid',
|
||||
|
@ -595,8 +592,8 @@ describe('Event Annotation Service', () => {
|
|||
type: 'tag',
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('updateAnnotationGroup', () => {
|
||||
|
@ -612,17 +609,17 @@ describe('Event Annotation Service', () => {
|
|||
},
|
||||
'multiAnnotations'
|
||||
);
|
||||
expect(core.savedObjects.client.update).toHaveBeenCalledWith(
|
||||
'event-annotation-group',
|
||||
'multiAnnotations',
|
||||
{
|
||||
expect(contentClient.update).toHaveBeenCalledWith({
|
||||
contentTypeId: 'event-annotation-group',
|
||||
id: 'multiAnnotations',
|
||||
data: {
|
||||
title: 'newTitle',
|
||||
description: '',
|
||||
annotations: [],
|
||||
dataViewSpec: null,
|
||||
dataViewSpec: undefined,
|
||||
ignoreGlobalFilters: false,
|
||||
} as EventAnnotationGroupAttributes,
|
||||
{
|
||||
options: {
|
||||
references: [
|
||||
{
|
||||
id: 'newId',
|
||||
|
@ -630,8 +627,8 @@ describe('Event Annotation Service', () => {
|
|||
type: 'index-pattern',
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,18 +13,16 @@ import { ExpressionAstExpression } from '@kbn/expressions-plugin/common';
|
|||
import {
|
||||
CoreStart,
|
||||
SavedObjectReference,
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsFindOptions,
|
||||
SavedObjectsFindOptionsReference,
|
||||
SimpleSavedObject,
|
||||
} from '@kbn/core/public';
|
||||
import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
|
||||
import { DataViewPersistableStateService } from '@kbn/data-views-plugin/common';
|
||||
import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
|
||||
import { defaultAnnotationLabel } from '../../common/manual_event_annotation';
|
||||
import { EventAnnotationGroupContent } from '../../common/types';
|
||||
import {
|
||||
EventAnnotationConfig,
|
||||
EventAnnotationGroupAttributes,
|
||||
EventAnnotationGroupConfig,
|
||||
EVENT_ANNOTATION_GROUP_TYPE,
|
||||
} from '../../common';
|
||||
|
@ -36,6 +34,20 @@ import {
|
|||
isQueryAnnotationConfig,
|
||||
} from './helpers';
|
||||
import { EventAnnotationGroupSavedObjectFinder } from '../components/event_annotation_group_saved_object_finder';
|
||||
import {
|
||||
EventAnnotationGroupCreateIn,
|
||||
EventAnnotationGroupCreateOut,
|
||||
EventAnnotationGroupDeleteIn,
|
||||
EventAnnotationGroupDeleteOut,
|
||||
EventAnnotationGroupGetIn,
|
||||
EventAnnotationGroupGetOut,
|
||||
EventAnnotationGroupSavedObject,
|
||||
EventAnnotationGroupSavedObjectAttributes,
|
||||
EventAnnotationGroupSearchIn,
|
||||
EventAnnotationGroupSearchOut,
|
||||
EventAnnotationGroupUpdateIn,
|
||||
EventAnnotationGroupUpdateOut,
|
||||
} from '../../common/content_management';
|
||||
|
||||
export function hasIcon(icon: string | undefined): icon is string {
|
||||
return icon != null && icon !== 'empty';
|
||||
|
@ -43,12 +55,13 @@ export function hasIcon(icon: string | undefined): icon is string {
|
|||
|
||||
export function getEventAnnotationService(
|
||||
core: CoreStart,
|
||||
contentManagement: ContentManagementPublicStart,
|
||||
savedObjectsManagement: SavedObjectsManagementPluginStart
|
||||
): EventAnnotationServiceType {
|
||||
const client: SavedObjectsClientContract = core.savedObjects.client;
|
||||
const client = contentManagement.client;
|
||||
|
||||
const mapSavedObjectToGroupConfig = (
|
||||
savedObject: SimpleSavedObject<EventAnnotationGroupAttributes>
|
||||
savedObject: EventAnnotationGroupSavedObject
|
||||
): EventAnnotationGroupConfig => {
|
||||
const adHocDataViewSpec = savedObject.attributes.dataViewSpec
|
||||
? DataViewPersistableStateService.inject(
|
||||
|
@ -71,7 +84,7 @@ export function getEventAnnotationService(
|
|||
};
|
||||
|
||||
const mapSavedObjectToGroupContent = (
|
||||
savedObject: SimpleSavedObject<EventAnnotationGroupAttributes>
|
||||
savedObject: EventAnnotationGroupSavedObject
|
||||
): EventAnnotationGroupContent => {
|
||||
const groupConfig = mapSavedObjectToGroupConfig(savedObject);
|
||||
|
||||
|
@ -92,16 +105,16 @@ export function getEventAnnotationService(
|
|||
const loadAnnotationGroup = async (
|
||||
savedObjectId: string
|
||||
): Promise<EventAnnotationGroupConfig> => {
|
||||
const savedObject = await client.get<EventAnnotationGroupAttributes>(
|
||||
EVENT_ANNOTATION_GROUP_TYPE,
|
||||
savedObjectId
|
||||
);
|
||||
const savedObject = await client.get<EventAnnotationGroupGetIn, EventAnnotationGroupGetOut>({
|
||||
contentTypeId: EVENT_ANNOTATION_GROUP_TYPE,
|
||||
id: savedObjectId,
|
||||
});
|
||||
|
||||
if (savedObject.error) {
|
||||
throw savedObject.error;
|
||||
if (savedObject.item.error) {
|
||||
throw savedObject.item.error;
|
||||
}
|
||||
|
||||
return mapSavedObjectToGroupConfig(savedObject);
|
||||
return mapSavedObjectToGroupConfig(savedObject.item);
|
||||
};
|
||||
|
||||
const findAnnotationGroupContent = async (
|
||||
|
@ -121,18 +134,29 @@ export function getEventAnnotationService(
|
|||
hasNoReference: referencesToExclude,
|
||||
};
|
||||
|
||||
const { total, savedObjects } = await client.find<EventAnnotationGroupAttributes>(
|
||||
searchOptions
|
||||
);
|
||||
const { pagination, hits } = await client.search<
|
||||
EventAnnotationGroupSearchIn,
|
||||
EventAnnotationGroupSearchOut
|
||||
>({
|
||||
contentTypeId: EVENT_ANNOTATION_GROUP_TYPE,
|
||||
query: {
|
||||
text: searchOptions.search,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
total,
|
||||
hits: savedObjects.map(mapSavedObjectToGroupContent),
|
||||
total: pagination.total,
|
||||
hits: hits.map(mapSavedObjectToGroupContent),
|
||||
};
|
||||
};
|
||||
|
||||
const deleteAnnotationGroups = async (ids: string[]): Promise<void> => {
|
||||
await client.bulkDelete([...ids.map((id) => ({ type: EVENT_ANNOTATION_GROUP_TYPE, id }))]);
|
||||
for (const id of ids) {
|
||||
await client.delete<EventAnnotationGroupDeleteIn, EventAnnotationGroupDeleteOut>({
|
||||
contentTypeId: EVENT_ANNOTATION_GROUP_TYPE,
|
||||
id,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const extractDataViewInformation = (group: EventAnnotationGroupConfig) => {
|
||||
|
@ -165,7 +189,10 @@ export function getEventAnnotationService(
|
|||
|
||||
const getAnnotationGroupAttributesAndReferences = (
|
||||
group: EventAnnotationGroupConfig
|
||||
): { attributes: EventAnnotationGroupAttributes; references: SavedObjectReference[] } => {
|
||||
): {
|
||||
attributes: EventAnnotationGroupSavedObjectAttributes;
|
||||
references: SavedObjectReference[];
|
||||
} => {
|
||||
const { references, dataViewSpec } = extractDataViewInformation(group);
|
||||
const { title, description, tags, ignoreGlobalFilters, annotations } = group;
|
||||
|
||||
|
@ -178,7 +205,13 @@ export function getEventAnnotationService(
|
|||
);
|
||||
|
||||
return {
|
||||
attributes: { title, description, ignoreGlobalFilters, annotations, dataViewSpec },
|
||||
attributes: {
|
||||
title,
|
||||
description,
|
||||
ignoreGlobalFilters,
|
||||
annotations,
|
||||
dataViewSpec: dataViewSpec || undefined,
|
||||
},
|
||||
references,
|
||||
};
|
||||
};
|
||||
|
@ -189,10 +222,16 @@ export function getEventAnnotationService(
|
|||
const { attributes, references } = getAnnotationGroupAttributesAndReferences(group);
|
||||
|
||||
const groupSavedObjectId = (
|
||||
await client.create(EVENT_ANNOTATION_GROUP_TYPE, attributes, {
|
||||
references,
|
||||
await client.create<EventAnnotationGroupCreateIn, EventAnnotationGroupCreateOut>({
|
||||
contentTypeId: EVENT_ANNOTATION_GROUP_TYPE,
|
||||
data: {
|
||||
...attributes,
|
||||
},
|
||||
options: {
|
||||
references,
|
||||
},
|
||||
})
|
||||
).id;
|
||||
).item.id;
|
||||
|
||||
return { id: groupSavedObjectId };
|
||||
};
|
||||
|
@ -203,18 +242,30 @@ export function getEventAnnotationService(
|
|||
): Promise<void> => {
|
||||
const { attributes, references } = getAnnotationGroupAttributesAndReferences(group);
|
||||
|
||||
await client.update(EVENT_ANNOTATION_GROUP_TYPE, annotationGroupId, attributes, {
|
||||
references,
|
||||
await client.update<EventAnnotationGroupUpdateIn, EventAnnotationGroupUpdateOut>({
|
||||
contentTypeId: EVENT_ANNOTATION_GROUP_TYPE,
|
||||
id: annotationGroupId,
|
||||
data: {
|
||||
...attributes,
|
||||
},
|
||||
options: {
|
||||
references,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const checkHasAnnotationGroups = async (): Promise<boolean> => {
|
||||
const response = await client.find({
|
||||
type: EVENT_ANNOTATION_GROUP_TYPE,
|
||||
perPage: 0,
|
||||
const response = await client.search<
|
||||
EventAnnotationGroupSearchIn,
|
||||
EventAnnotationGroupSearchOut
|
||||
>({
|
||||
contentTypeId: EVENT_ANNOTATION_GROUP_TYPE,
|
||||
query: {
|
||||
text: '*',
|
||||
},
|
||||
});
|
||||
|
||||
return response.total > 0;
|
||||
return response.pagination.total > 0;
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
@ -8,10 +8,19 @@
|
|||
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
|
||||
import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
|
||||
import { getEventAnnotationService } from './event_annotation_service/service';
|
||||
|
||||
// not really mocking but avoiding async loading
|
||||
export const eventAnnotationServiceMock = getEventAnnotationService(
|
||||
coreMock.createStart(),
|
||||
{
|
||||
client: {
|
||||
get: jest.fn(),
|
||||
search: jest.fn(),
|
||||
create: jest.fn(),
|
||||
update: jest.fn(),
|
||||
},
|
||||
} as unknown as ContentManagementPublicStart,
|
||||
{} as SavedObjectsManagementPluginStart
|
||||
);
|
||||
|
|
|
@ -12,6 +12,10 @@ import type { SavedObjectTaggingPluginStart } from '@kbn/saved-objects-tagging-p
|
|||
import type { ExpressionsSetup } from '@kbn/expressions-plugin/public';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
|
||||
import {
|
||||
ContentManagementPublicSetup,
|
||||
ContentManagementPublicStart,
|
||||
} from '@kbn/content-management-plugin/public';
|
||||
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public/types';
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { VisualizationsSetup } from '@kbn/visualizations-plugin/public';
|
||||
|
@ -27,6 +31,7 @@ import {
|
|||
import { getFetchEventAnnotations } from './fetch_event_annotations';
|
||||
import type { EventAnnotationListingPageServices } from './get_table_list';
|
||||
import { ANNOTATIONS_LISTING_VIEW_ID } from '../common/constants';
|
||||
import { CONTENT_ID, LATEST_VERSION } from '../common/content_management';
|
||||
|
||||
export interface EventAnnotationStartDependencies {
|
||||
savedObjectsManagement: SavedObjectsManagementPluginStart;
|
||||
|
@ -35,11 +40,13 @@ export interface EventAnnotationStartDependencies {
|
|||
presentationUtil: PresentationUtilPluginStart;
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
unifiedSearch: UnifiedSearchPublicPluginStart;
|
||||
contentManagement: ContentManagementPublicStart;
|
||||
}
|
||||
|
||||
interface SetupDependencies {
|
||||
expressions: ExpressionsSetup;
|
||||
visualizations: VisualizationsSetup;
|
||||
contentManagement: ContentManagementPublicSetup;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
@ -62,6 +69,16 @@ export class EventAnnotationPlugin
|
|||
getFetchEventAnnotations({ getStartServices: core.getStartServices })
|
||||
);
|
||||
|
||||
dependencies.contentManagement.registry.register({
|
||||
id: CONTENT_ID,
|
||||
version: {
|
||||
latest: LATEST_VERSION,
|
||||
},
|
||||
name: i18n.translate('eventAnnotation.content.name', {
|
||||
defaultMessage: 'Annotation group',
|
||||
}),
|
||||
});
|
||||
|
||||
dependencies.visualizations.listingViewRegistry.add({
|
||||
title: i18n.translate('eventAnnotation.listingViewTitle', {
|
||||
defaultMessage: 'Annotation groups',
|
||||
|
@ -72,6 +89,7 @@ export class EventAnnotationPlugin
|
|||
|
||||
const eventAnnotationService = await new EventAnnotationService(
|
||||
coreStart,
|
||||
pluginsStart.contentManagement,
|
||||
pluginsStart.savedObjectsManagement
|
||||
).getService();
|
||||
|
||||
|
@ -107,6 +125,10 @@ export class EventAnnotationPlugin
|
|||
core: CoreStart,
|
||||
startDependencies: EventAnnotationStartDependencies
|
||||
): EventAnnotationService {
|
||||
return new EventAnnotationService(core, startDependencies.savedObjectsManagement);
|
||||
return new EventAnnotationService(
|
||||
core,
|
||||
startDependencies.contentManagement,
|
||||
startDependencies.savedObjectsManagement
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,324 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* 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 { EVENT_ANNOTATION_GROUP_TYPE } from '../../common';
|
||||
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';
|
||||
|
||||
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>
|
||||
{
|
||||
constructor() {}
|
||||
|
||||
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);
|
||||
|
||||
// Save data in DB
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { EventAnnotationGroupStorage } from './event_annotation_group_storage';
|
|
@ -9,6 +9,7 @@
|
|||
import { CoreSetup, Plugin } 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';
|
||||
import {
|
||||
manualPointEventAnnotation,
|
||||
eventAnnotationGroup,
|
||||
|
@ -16,9 +17,12 @@ import {
|
|||
queryPointEventAnnotation,
|
||||
} from '../common';
|
||||
import { setupSavedObjects } from './saved_objects';
|
||||
import { EventAnnotationGroupStorage } from './content_management';
|
||||
import { CONTENT_ID, LATEST_VERSION } from '../common/content_management';
|
||||
|
||||
interface SetupDependencies {
|
||||
expressions: ExpressionsServerSetup;
|
||||
contentManagement: ContentManagementServerSetup;
|
||||
}
|
||||
export interface EventAnnotationStartDependencies {
|
||||
data: DataPluginStart;
|
||||
|
@ -36,6 +40,14 @@ export class EventAnnotationServerPlugin implements Plugin<object, object> {
|
|||
|
||||
setupSavedObjects(core);
|
||||
|
||||
dependencies.contentManagement.register({
|
||||
id: CONTENT_ID,
|
||||
storage: new EventAnnotationGroupStorage(),
|
||||
version: {
|
||||
latest: LATEST_VERSION,
|
||||
},
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
import { DataViewPersistableStateService } from '@kbn/data-views-plugin/common';
|
||||
import { VISUALIZE_APP_NAME } from '@kbn/visualizations-plugin/common/constants';
|
||||
import { ANNOTATIONS_LISTING_VIEW_ID, EVENT_ANNOTATION_GROUP_TYPE } from '../common/constants';
|
||||
import { EventAnnotationGroupAttributes } from '../common/types';
|
||||
import { EventAnnotationGroupSavedObjectAttributes } from '../common';
|
||||
|
||||
export function setupSavedObjects(coreSetup: CoreSetup) {
|
||||
coreSetup.savedObjects.registerType({
|
||||
|
@ -28,7 +28,8 @@ export function setupSavedObjects(coreSetup: CoreSetup) {
|
|||
icon: 'flag',
|
||||
defaultSearchField: 'title',
|
||||
importableAndExportable: true,
|
||||
getTitle: (obj: { attributes: EventAnnotationGroupAttributes }) => obj.attributes.title,
|
||||
getTitle: (obj: { attributes: EventAnnotationGroupSavedObjectAttributes }) =>
|
||||
obj.attributes.title,
|
||||
getInAppUrl: (obj: { id: string }) => ({
|
||||
// TODO link to specific object
|
||||
path: `/app/${VISUALIZE_APP_NAME}#/${ANNOTATIONS_LISTING_VIEW_ID}`,
|
||||
|
|
|
@ -44,6 +44,11 @@
|
|||
"@kbn/content-management-tabbed-table-list-view",
|
||||
"@kbn/core-notifications-browser",
|
||||
"@kbn/core-notifications-browser-mocks",
|
||||
"@kbn/core-saved-objects-server",
|
||||
"@kbn/object-versioning",
|
||||
"@kbn/config-schema",
|
||||
"@kbn/content-management-plugin",
|
||||
"@kbn/core-saved-objects-api-server"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue