diff --git a/src/plugins/event_annotation/common/content_management/v1/cm_services.ts b/src/plugins/event_annotation/common/content_management/v1/cm_services.ts index 8da6efc7c7f08..f5f7b0ac83b0e 100644 --- a/src/plugins/event_annotation/common/content_management/v1/cm_services.ts +++ b/src/plugins/event_annotation/common/content_management/v1/cm_services.ts @@ -79,7 +79,6 @@ const getResultSchema = schema.object( ); const createOptionsSchema = schema.object({ - overwrite: schema.maybe(schema.boolean()), references: schema.maybe(referencesSchema), }); diff --git a/src/plugins/event_annotation/common/content_management/v1/types.ts b/src/plugins/event_annotation/common/content_management/v1/types.ts index 67ba080a78151..5996a6f0db455 100644 --- a/src/plugins/event_annotation/common/content_management/v1/types.ts +++ b/src/plugins/event_annotation/common/content_management/v1/types.ts @@ -79,8 +79,6 @@ export type EventAnnotationGroupGetOut = GetResult< // ----------- 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[]; } diff --git a/src/plugins/event_annotation/common/index.ts b/src/plugins/event_annotation/common/index.ts index cb30e43060cfb..a99204be39694 100644 --- a/src/plugins/event_annotation/common/index.ts +++ b/src/plugins/event_annotation/common/index.ts @@ -27,10 +27,16 @@ export type { FetchEventAnnotationsArgs } from './fetch_event_annotations/types' export type { EventAnnotationArgs, EventAnnotationOutput } from './types'; export type { + EventAnnotationGroupGetIn, + EventAnnotationGroupGetOut, EventAnnotationGroupSavedObjectAttributes, EventAnnotationGroupCreateIn, + EventAnnotationGroupCreateOut, EventAnnotationGroupUpdateIn, EventAnnotationGroupSearchIn, + EventAnnotationGroupSearchOut, + EventAnnotationGroupDeleteIn, + EventAnnotationGroupDeleteOut, } from './content_management'; export { CONTENT_ID } from './content_management'; export { ANNOTATIONS_LISTING_VIEW_ID } from './constants'; diff --git a/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts b/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts index f71e9cd72b43b..dcb25deb71140 100644 --- a/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts +++ b/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts @@ -121,7 +121,6 @@ export class EventAnnotationGroupStorage const transforms = getTransforms(cmServicesDefinition, requestVersion); const soClient = await savedObjectClientFromRequest(ctx); - // Save data in DB const { saved_object: savedObject, alias_purpose: aliasPurpose, diff --git a/test/api_integration/apis/event_annotations/event_annotations.ts b/test/api_integration/apis/event_annotations/event_annotations.ts index fda97881f3d9b..fc4a313279b02 100644 --- a/test/api_integration/apis/event_annotations/event_annotations.ts +++ b/test/api_integration/apis/event_annotations/event_annotations.ts @@ -10,18 +10,29 @@ import expect from '@kbn/expect'; import type { EventAnnotationGroupSavedObjectAttributes, EventAnnotationGroupCreateIn, + EventAnnotationGroupCreateOut, EventAnnotationGroupUpdateIn, EventAnnotationGroupSearchIn, + EventAnnotationGroupSearchOut, + EventAnnotationGroupGetIn, + EventAnnotationGroupGetOut, + EventAnnotationGroupDeleteIn, + EventAnnotationGroupDeleteOut, } from '@kbn/event-annotation-plugin/common'; import { CONTENT_ID } from '@kbn/event-annotation-plugin/common'; +import { EVENT_ANNOTATION_GROUP_TYPE } from '@kbn/event-annotation-common'; import { FtrProviderContext } from '../../ftr_provider_context'; const CONTENT_ENDPOINT = '/api/content_management/rpc'; const API_VERSION = 1; -const EXISTING_ID_1 = 'fcebef20-3ba4-11ee-85d3-3dd00bdd66ef'; // from loaded archive -const EXISTING_ID_2 = '0d1aa670-3baf-11ee-a4a7-c11cb33a9549'; // from loaded archive +// IDs come from from loaded archive +const EXISTING_ID_1 = '46c2a460-4e77-11ee-bb97-116581699678'; +const EXISTING_ID_2 = '425d2760-4e77-11ee-bb97-116581699678'; +const DESCRIPTION_2 = 'i am a description you can search for!'; +const EXISTING_ID_3 = '3905a4d0-4e77-11ee-bb97-116581699678'; +const TAG_ID = '36a8f020-4e77-11ee-bb97-116581699678'; const DEFAULT_EVENT_ANNOTATION_GROUP: EventAnnotationGroupSavedObjectAttributes = { title: 'a group', @@ -56,6 +67,8 @@ export default function ({ getService }: FtrProviderContext) { describe('group API', () => { before(async () => { + await kibanaServer.savedObjects.clean({ types: [EVENT_ANNOTATION_GROUP_TYPE] }); + await kibanaServer.importExport.load( 'test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json' ); @@ -65,10 +78,79 @@ export default function ({ getService }: FtrProviderContext) { await kibanaServer.importExport.unload( 'test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json' ); + + await kibanaServer.savedObjects.clean({ types: [EVENT_ANNOTATION_GROUP_TYPE] }); + }); + + describe('get', () => { + it(`should retrieve an existing group`, async () => { + const payload: EventAnnotationGroupGetIn = { + contentTypeId: CONTENT_ID, + id: EXISTING_ID_1, + version: API_VERSION, + }; + + const resp = await supertest + .post(`${CONTENT_ENDPOINT}/get`) + .set('kbn-xsrf', 'kibana') + .send(payload) + .expect(200); + + const result = resp.body.result.result as EventAnnotationGroupGetOut; + + expect(result.item.id).to.be(EXISTING_ID_1); + expect(result.meta.outcome).to.be('exactMatch'); + expect(result.item.references.length).to.be(1); + expect(result.item.attributes).to.eql({ + annotations: [ + { + filter: { + language: 'kuery', + query: + 'agent.keyword : "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)" ', + type: 'kibana_query', + }, + icon: 'triangle', + id: 'fdede168-eff1-400f-b106-0f62061f5099', + key: { + type: 'point_in_time', + }, + label: 'Event', + timeField: 'timestamp', + type: 'query', + }, + ], + dataViewSpec: null, + description: '', + ignoreGlobalFilters: true, + title: 'group3', + }); + }); + + it(`should reject a group that does not exist`, async () => { + const payload: EventAnnotationGroupGetIn = { + contentTypeId: CONTENT_ID, + id: 'does-not-exist', + version: API_VERSION, + }; + + const resp = await supertest + .post(`${CONTENT_ENDPOINT}/get`) + .set('kbn-xsrf', 'kibana') + .send(payload) + .expect(404); + + expect(resp.body).to.eql({ + error: 'Not Found', + message: 'Saved object [event-annotation-group/does-not-exist] not found', + statusCode: 404, + }); + }); }); describe('search', () => { - // TODO test tag searching, ordering, pagination, etc + const performSearch = (payload: EventAnnotationGroupSearchIn) => + supertest.post(`${CONTENT_ENDPOINT}/search`).set('kbn-xsrf', 'kibana').send(payload); it(`should retrieve existing groups`, async () => { const payload: EventAnnotationGroupSearchIn = { @@ -83,19 +165,172 @@ export default function ({ getService }: FtrProviderContext) { version: API_VERSION, }; + const resp = await performSearch(payload).expect(200); + + const results = resp.body.result.result.hits; + expect(results.length).to.be(3); + expect(results.map(({ id }: { id: string }) => id)).to.eql([ + EXISTING_ID_1, + EXISTING_ID_2, + EXISTING_ID_3, + ]); + }); + + it(`should filter by tag`, async () => { + const payload: EventAnnotationGroupSearchIn = { + contentTypeId: CONTENT_ID, + query: { + limit: 1000, + tags: { + included: [TAG_ID], + excluded: [], + }, + }, + version: API_VERSION, + }; + + const resp = await performSearch(payload).expect(200); + + const result = resp.body.result.result as EventAnnotationGroupSearchOut; + expect(result.hits.length).to.be(2); + expect( + result.hits.every(({ references }) => references.map(({ id }) => id).includes(TAG_ID)) + ).to.be(true); + }); + + it(`should filter by text`, async () => { + const payload: EventAnnotationGroupSearchIn = { + contentTypeId: CONTENT_ID, + query: { + limit: 1000, + text: DESCRIPTION_2, + tags: { + included: [], + excluded: [], + }, + }, + version: API_VERSION, + }; + + const resp = await performSearch(payload).expect(200); + + const result = resp.body.result.result as EventAnnotationGroupSearchOut; + expect(result.hits.length).to.be(1); + expect(result.hits[0].id).to.be(EXISTING_ID_2); + }); + + it(`should paginate`, async () => { + const payload: EventAnnotationGroupSearchIn = { + contentTypeId: CONTENT_ID, + query: { + limit: 1, + cursor: '1', + tags: { + included: [], + excluded: [], + }, + }, + version: API_VERSION, + }; + + const resp = await performSearch(payload).expect(200); + + const result = resp.body.result.result as EventAnnotationGroupSearchOut; + expect(result.hits.length).to.be(1); + expect(result.hits[0].id).to.be(EXISTING_ID_1); + expect(result.pagination.total).to.be(3); + + // get second page + payload.query.cursor = '2'; + + const resp2 = await performSearch(payload).expect(200); + + const result2 = resp2.body.result.result as EventAnnotationGroupSearchOut; + expect(result2.hits.length).to.be(1); + expect(result2.hits[0].id).to.be(EXISTING_ID_2); + expect(result2.pagination.total).to.be(3); + + // get third page + payload.query.cursor = '3'; + + const resp3 = await performSearch(payload).expect(200); + + const result3 = resp3.body.result.result as EventAnnotationGroupSearchOut; + expect(result3.hits.length).to.be(1); + expect(result3.hits[0].id).to.be(EXISTING_ID_3); + expect(result3.pagination.total).to.be(3); + }); + }); + + describe('create', () => { + it(`should create a new group`, async () => { + const payload: EventAnnotationGroupCreateIn = { + contentTypeId: CONTENT_ID, + data: DEFAULT_EVENT_ANNOTATION_GROUP, + options: { + references: DEFAULT_REFERENCES, + }, + version: API_VERSION, + }; + const resp = await supertest - .post(`${CONTENT_ENDPOINT}/search`) + .post(`${CONTENT_ENDPOINT}/create`) .set('kbn-xsrf', 'kibana') .send(payload) .expect(200); - const results = resp.body.result.result.hits; - expect(results.length).to.be(2); - expect(results.map(({ id }: { id: string }) => id)).to.eql([EXISTING_ID_2, EXISTING_ID_1]); + const result = resp.body.result.result as EventAnnotationGroupCreateOut; + + expect(result.item.attributes).to.eql(DEFAULT_EVENT_ANNOTATION_GROUP); + expect(result.item.id).to.be.a('string'); + expect(result.item.namespaces).to.eql(['default']); + }); + + it(`should reject malformed groups`, async () => { + const badGroups = [ + // extra property + { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + extraProp: 'some-value', + }, + // missing title + { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + title: undefined, + }, + // wrong type for property + { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + ignoreGlobalFilters: 'not-a-boolean', + }, + ] as unknown as EventAnnotationGroupSavedObjectAttributes[]; // (coerce the types because these are intentionally malformed) + + const expectedMessages = [ + 'Invalid data. [extraProp]: definition for this key is missing', + 'Invalid data. [title]: expected value of type [string] but got [undefined]', + 'Invalid data. [ignoreGlobalFilters]: expected value of type [boolean] but got [string]', + ]; + + for (let i = 0; i < badGroups.length; i++) { + const payload: EventAnnotationGroupCreateIn = { + contentTypeId: CONTENT_ID, + data: badGroups[i], + options: { + references: DEFAULT_REFERENCES, + }, + version: API_VERSION, + }; + + const resp = await supertest + .post(`${CONTENT_ENDPOINT}/create`) + .set('kbn-xsrf', 'kibana') + .send(payload) + .expect(400); + + expect(resp.body.message).to.be(expectedMessages[i]); + } }); - }); - describe('create', () => { it(`should require dataViewSpec to be specified`, async () => { const createWithDataViewSpec = (dataViewSpec: any) => { const payload: EventAnnotationGroupCreateIn = { @@ -127,6 +362,81 @@ export default function ({ getService }: FtrProviderContext) { }); describe('update', () => { + it(`should update a group`, async () => { + const payload: EventAnnotationGroupUpdateIn = { + contentTypeId: CONTENT_ID, + data: { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + description: 'updated description', + }, + id: EXISTING_ID_1, + options: { + references: DEFAULT_REFERENCES, + }, + version: API_VERSION, + }; + + const resp = await supertest + .post(`${CONTENT_ENDPOINT}/update`) + .set('kbn-xsrf', 'kibana') + .send(payload) + .expect(200); + + const result = resp.body.result.result as EventAnnotationGroupCreateOut; + + expect(result.item.attributes).to.eql({ + ...DEFAULT_EVENT_ANNOTATION_GROUP, + description: 'updated description', + }); + expect(result.item.id).to.be(EXISTING_ID_1); + }); + + it(`should reject malformed groups`, async () => { + const badGroups = [ + // extra property + { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + extraProp: 'some-value', + }, + // missing title + { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + title: undefined, + }, + // wrong type for property + { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + ignoreGlobalFilters: 'not-a-boolean', + }, + ] as unknown as EventAnnotationGroupSavedObjectAttributes[]; // (coerce the types because these are intentionally malformed) + + const expectedMessages = [ + 'Invalid data. [extraProp]: definition for this key is missing', + 'Invalid data. [title]: expected value of type [string] but got [undefined]', + 'Invalid data. [ignoreGlobalFilters]: expected value of type [boolean] but got [string]', + ]; + + for (let i = 0; i < badGroups.length; i++) { + const payload: EventAnnotationGroupUpdateIn = { + contentTypeId: CONTENT_ID, + data: badGroups[i], + id: EXISTING_ID_1, + options: { + references: DEFAULT_REFERENCES, + }, + version: API_VERSION, + }; + + const resp = await supertest + .post(`${CONTENT_ENDPOINT}/update`) + .set('kbn-xsrf', 'kibana') + .send(payload) + .expect(400); + + expect(resp.body.message).to.be(expectedMessages[i]); + } + }); + it(`should require dataViewSpec to be specified`, async () => { const updateWithDataViewSpec = (dataViewSpec: any) => { const payload: EventAnnotationGroupUpdateIn = { @@ -158,6 +468,34 @@ export default function ({ getService }: FtrProviderContext) { }); }); - // TODO - delete + describe('delete', () => { + const deleteGroupByID = (id: string) => { + const payload: EventAnnotationGroupDeleteIn = { + contentTypeId: CONTENT_ID, + id, + version: API_VERSION, + }; + + return supertest.post(`${CONTENT_ENDPOINT}/delete`).set('kbn-xsrf', 'kibana').send(payload); + }; + + it(`should delete a group`, async () => { + const resp = await deleteGroupByID(EXISTING_ID_1).expect(200); + + const result = resp.body.result.result as EventAnnotationGroupDeleteOut; + + expect(result.success).to.be(true); + }); + + it(`should reject deleting a group that does not exist`, async () => { + const resp = await deleteGroupByID('does-not-exist').expect(404); + + expect(resp.body).to.eql({ + error: 'Not Found', + message: 'Saved object [event-annotation-group/does-not-exist] not found', + statusCode: 404, + }); + }); + }); }); } diff --git a/test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json b/test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json index ed574c749518c..4732d061c6930 100644 --- a/test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json +++ b/test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json @@ -7,33 +7,31 @@ "title": "kibana_sample_data_logs" }, "coreMigrationVersion": "8.8.0", - "created_at": "2023-08-15T19:49:25.494Z", + "created_at": "2023-09-07T17:23:20.906Z", "id": "90943e30-9a47-11e8-b64d-95841ca0b247", "managed": false, "references": [], "type": "index-pattern", "typeMigrationVersion": "8.0.0", - "updated_at": "2023-08-15T19:49:25.494Z", - "version": "WzIyLDFd" + "updated_at": "2023-09-07T17:23:20.906Z", + "version": "WzEwMiwxXQ==" } { "attributes": { "annotations": [ { - "color": "#6092c0", "filter": { "language": "kuery", "query": "agent.keyword : \"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\" ", "type": "kibana_query" }, - "icon": "asterisk", - "id": "499ee351-f541-46e0-b327-b3dcae91aff5", + "icon": "triangle", + "id": "fdede168-eff1-400f-b106-0f62061f5099", "key": { "type": "point_in_time" }, "label": "Event", - "lineStyle": "dashed", "timeField": "timestamp", "type": "query" } @@ -41,11 +39,11 @@ "dataViewSpec": null, "description": "", "ignoreGlobalFilters": true, - "title": "Another group" + "title": "group3" }, "coreMigrationVersion": "8.8.0", - "created_at": "2023-08-15T21:02:32.023Z", - "id": "0d1aa670-3baf-11ee-a4a7-c11cb33a9549", + "created_at": "2023-09-08T18:41:09.030Z", + "id": "46c2a460-4e77-11ee-bb97-116581699678", "managed": false, "references": [ { @@ -55,8 +53,25 @@ } ], "type": "event-annotation-group", - "updated_at": "2023-08-15T22:15:43.724Z", - "version": "WzU4LDFd" + "updated_at": "2023-09-08T18:42:48.080Z", + "version": "WzE2OCwxXQ==" +} + +{ + "attributes": { + "color": "#dac7c4", + "description": "a tag to filter by", + "name": "tag" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-08T18:40:42.018Z", + "id": "36a8f020-4e77-11ee-bb97-116581699678", + "managed": false, + "references": [], + "type": "tag", + "typeMigrationVersion": "8.0.0", + "updated_at": "2023-09-08T18:40:42.018Z", + "version": "WzI3NDUsMV0=" } { @@ -64,9 +79,49 @@ "annotations": [ { "icon": "triangle", - "id": "1d9627a8-11dc-44f1-badb-4d40a80b6bee", + "id": "3d9f03ca-36aa-4ebb-aab8-efef1591c6d5", "key": { - "timestamp": "2023-08-10T15:00:00.000Z", + "timestamp": "2023-09-08T18:32:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "i am a description you can search for!", + "ignoreGlobalFilters": true, + "title": "group2" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-08T18:41:01.654Z", + "id": "425d2760-4e77-11ee-bb97-116581699678", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + }, + { + "id": "36a8f020-4e77-11ee-bb97-116581699678", + "name": "36a8f020-4e77-11ee-bb97-116581699678", + "type": "tag" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-08T18:41:01.654Z", + "version": "WzE2NCwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "f7be288b-8adf-48b9-89d4-267db4863a3d", + "key": { + "timestamp": "2023-09-08T18:32:00.000Z", "type": "point_in_time" }, "label": "Event", @@ -76,20 +131,25 @@ "dataViewSpec": null, "description": "", "ignoreGlobalFilters": true, - "title": "A group" + "title": "group1" }, "coreMigrationVersion": "8.8.0", - "created_at": "2023-08-15T19:50:29.907Z", - "id": "fcebef20-3ba4-11ee-85d3-3dd00bdd66ef", + "created_at": "2023-09-08T18:40:45.981Z", + "id": "3905a4d0-4e77-11ee-bb97-116581699678", "managed": false, "references": [ { "id": "90943e30-9a47-11e8-b64d-95841ca0b247", "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", "type": "index-pattern" + }, + { + "id": "36a8f020-4e77-11ee-bb97-116581699678", + "name": "36a8f020-4e77-11ee-bb97-116581699678", + "type": "tag" } ], "type": "event-annotation-group", - "updated_at": "2023-08-15T22:13:19.290Z", - "version": "WzU0LDFd" + "updated_at": "2023-09-08T18:40:45.981Z", + "version": "WzE2MiwxXQ==" } \ No newline at end of file diff --git a/test/tsconfig.json b/test/tsconfig.json index 56d4f185930e7..d71c7f9c8ffb1 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -68,6 +68,7 @@ "@kbn/core-saved-objects-server", "@kbn/discover-plugin", "@kbn/core-http-common", - "@kbn/event-annotation-plugin" + "@kbn/event-annotation-plugin", + "@kbn/event-annotation-common" ] }