Skip to content

Commit

Permalink
[Cases] Add import/export functionality (#110148)
Browse files Browse the repository at this point in the history
* Working export but not import

* Adding user action import flag

* Adding a few integration tests

* Adding connector test

* Finishing integration tests

* Removing connector after each test to restore original state

* Bumping migration version for comment so

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
jonathan-buttner and kibanamachine authored Sep 14, 2021
1 parent 65eb7b9 commit 2567c4f
Show file tree
Hide file tree
Showing 10 changed files with 369 additions and 7 deletions.
4 changes: 2 additions & 2 deletions x-pack/plugins/cases/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
createCaseCommentSavedObjectType,
caseConfigureSavedObjectType,
caseConnectorMappingsSavedObjectType,
caseSavedObjectType,
createCaseSavedObjectType,
caseUserActionSavedObjectType,
subCaseSavedObjectType,
} from './saved_object_types';
Expand Down Expand Up @@ -94,7 +94,7 @@ export class CasePlugin {
);
core.savedObjects.registerType(caseConfigureSavedObjectType);
core.savedObjects.registerType(caseConnectorMappingsSavedObjectType);
core.savedObjects.registerType(caseSavedObjectType);
core.savedObjects.registerType(createCaseSavedObjectType(core, this.log));
core.savedObjects.registerType(caseUserActionSavedObjectType);

this.log.debug(
Expand Down
27 changes: 24 additions & 3 deletions x-pack/plugins/cases/server/saved_object_types/cases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,22 @@
* 2.0.
*/

import { SavedObjectsType } from 'src/core/server';
import {
CoreSetup,
Logger,
SavedObject,
SavedObjectsExportTransformContext,
SavedObjectsType,
} from 'src/core/server';
import { CASE_SAVED_OBJECT } from '../../common';
import { ESCaseAttributes } from '../services/cases/types';
import { handleExport } from './import_export/export';
import { caseMigrations } from './migrations';

export const caseSavedObjectType: SavedObjectsType = {
export const createCaseSavedObjectType = (
coreSetup: CoreSetup,
logger: Logger
): SavedObjectsType => ({
name: CASE_SAVED_OBJECT,
hidden: true,
namespaceType: 'single',
Expand Down Expand Up @@ -144,4 +155,14 @@ export const caseSavedObjectType: SavedObjectsType = {
},
},
migrations: caseMigrations,
};
management: {
importableAndExportable: true,
defaultSearchField: 'title',
icon: 'folderExclamation',
getTitle: (savedObject: SavedObject<ESCaseAttributes>) => savedObject.attributes.title,
onExport: async (
context: SavedObjectsExportTransformContext,
objects: Array<SavedObject<ESCaseAttributes>>
) => handleExport({ context, objects, coreSetup, logger }),
},
});
5 changes: 4 additions & 1 deletion x-pack/plugins/cases/server/saved_object_types/comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,8 @@ export const createCaseCommentSavedObjectType = ({
},
},
},
migrations: () => createCommentsMigrations(migrationDeps),
migrations: createCommentsMigrations(migrationDeps),
management: {
importableAndExportable: true,
},
});
114 changes: 114 additions & 0 deletions x-pack/plugins/cases/server/saved_object_types/import_export/export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import {
CoreSetup,
Logger,
SavedObject,
SavedObjectsClientContract,
SavedObjectsExportTransformContext,
} from 'kibana/server';
import {
CaseUserActionAttributes,
CASE_COMMENT_SAVED_OBJECT,
CASE_SAVED_OBJECT,
CASE_USER_ACTION_SAVED_OBJECT,
CommentAttributes,
MAX_DOCS_PER_PAGE,
SAVED_OBJECT_TYPES,
} from '../../../common';
import { createCaseError, defaultSortField } from '../../common';
import { ESCaseAttributes } from '../../services/cases/types';

export async function handleExport({
context,
objects,
coreSetup,
logger,
}: {
context: SavedObjectsExportTransformContext;
objects: Array<SavedObject<ESCaseAttributes>>;
coreSetup: CoreSetup;
logger: Logger;
}): Promise<Array<SavedObject<ESCaseAttributes | CommentAttributes | CaseUserActionAttributes>>> {
try {
if (objects.length <= 0) {
return [];
}

const [{ savedObjects }] = await coreSetup.getStartServices();
const savedObjectsClient = savedObjects.getScopedClient(context.request, {
includedHiddenTypes: SAVED_OBJECT_TYPES,
});

const caseIds = objects.map((caseObject) => caseObject.id);
const attachmentsAndUserActionsForCases = await getAttachmentsAndUserActionsForCases(
savedObjectsClient,
caseIds
);

return [...objects, ...attachmentsAndUserActionsForCases.flat()];
} catch (error) {
throw createCaseError({
message: `Failed to retrieve associated objects for exporting of cases: ${error}`,
error,
logger,
});
}
}

async function getAttachmentsAndUserActionsForCases(
savedObjectsClient: SavedObjectsClientContract,
caseIds: string[]
): Promise<Array<SavedObject<CommentAttributes | CaseUserActionAttributes>>> {
const [attachments, userActions] = await Promise.all([
getAssociatedObjects<CommentAttributes>({
savedObjectsClient,
caseIds,
sortField: defaultSortField,
type: CASE_COMMENT_SAVED_OBJECT,
}),
getAssociatedObjects<CaseUserActionAttributes>({
savedObjectsClient,
caseIds,
sortField: 'action_at',
type: CASE_USER_ACTION_SAVED_OBJECT,
}),
]);

return [...attachments, ...userActions];
}

async function getAssociatedObjects<T>({
savedObjectsClient,
caseIds,
sortField,
type,
}: {
savedObjectsClient: SavedObjectsClientContract;
caseIds: string[];
sortField: string;
type: string;
}): Promise<Array<SavedObject<T>>> {
const references = caseIds.map((id) => ({ type: CASE_SAVED_OBJECT, id }));

const finder = savedObjectsClient.createPointInTimeFinder<T>({
type,
hasReferenceOperator: 'OR',
hasReference: references,
perPage: MAX_DOCS_PER_PAGE,
sortField,
sortOrder: 'asc',
});

let result: Array<SavedObject<T>> = [];
for await (const findResults of finder.find()) {
result = result.concat(findResults.saved_objects);
}

return result;
}
2 changes: 1 addition & 1 deletion x-pack/plugins/cases/server/saved_object_types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

export { caseSavedObjectType } from './cases';
export { createCaseSavedObjectType } from './cases';
export { subCaseSavedObjectType } from './sub_case';
export { caseConfigureSavedObjectType } from './configure';
export { createCaseCommentSavedObjectType } from './comments';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,7 @@ export const caseUserActionSavedObjectType: SavedObjectsType = {
},
},
migrations: userActionsMigrations,
management: {
importableAndExportable: true,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{"attributes":{"closed_at":null,"closed_by":null,"connector":{"fields":[],"name":"none","type":".none"},"created_at":"2021-08-26T19:48:01.292Z","created_by":{"email":null,"full_name":null,"username":"elastic"},"description":"a description","external_service":null,"owner":"securitySolution","settings":{"syncAlerts":true},"status":"open","tags":["some tags"],"title":"A case to export","type":"individual","updated_at":"2021-08-26T19:48:30.151Z","updated_by":{"email":null,"full_name":null,"username":"elastic"}},"coreMigrationVersion":"8.0.0","id":"85541260-06a6-11ec-b3f9-3d05c48a7d46","migrationVersion":{"cases":"7.15.0"},"references":[],"type":"cases","updated_at":"2021-08-26T19:48:30.162Z","version":"WzM0NDEsMV0="}
{"attributes":{"action":"create","action_at":"2021-08-26T19:48:01.292Z","action_by":{"email":null,"full_name":null,"username":"elastic"},"action_field":["description","status","tags","title","connector","settings","owner"],"new_value":"{\"type\":\"individual\",\"title\":\"A case to export\",\"tags\":[\"some tags\"],\"description\":\"a description\",\"connector\":{\"id\":\"none\",\"name\":\"none\",\"type\":\".none\",\"fields\":null},\"settings\":{\"syncAlerts\":true},\"owner\":\"securitySolution\"}","old_value":null,"owner":"securitySolution"},"coreMigrationVersion":"8.0.0","id":"8cb85070-06a6-11ec-b3f9-3d05c48a7d46","migrationVersion":{"cases-user-actions":"7.14.0"},"references":[{"id":"85541260-06a6-11ec-b3f9-3d05c48a7d46","name":"associated-cases","type":"cases"}],"score":null,"sort":[1630007281292,7288],"type":"cases-user-actions","updated_at":"2021-08-26T19:48:13.687Z","version":"WzIzODIsMV0="}
{"attributes":{"associationType":"case","comment":"A comment for my case","created_at":"2021-08-26T19:48:30.151Z","created_by":{"email":null,"full_name":null,"username":"elastic"},"owner":"securitySolution","pushed_at":null,"pushed_by":null,"type":"user","updated_at":null,"updated_by":null},"coreMigrationVersion":"8.0.0","id":"9687c220-06a6-11ec-b3f9-3d05c48a7d46","migrationVersion":{"cases-comments":"7.16.0"},"references":[{"id":"85541260-06a6-11ec-b3f9-3d05c48a7d46","name":"associated-cases","type":"cases"}],"score":null,"sort":[1630007310151,9470],"type":"cases-comments","updated_at":"2021-08-26T19:48:30.161Z","version":"WzM0NDIsMV0="}
{"attributes":{"action":"create","action_at":"2021-08-26T19:48:30.151Z","action_by":{"email":null,"full_name":null,"username":"elastic"},"action_field":["comment"],"new_value":"{\"comment\":\"A comment for my case\",\"type\":\"user\",\"owner\":\"securitySolution\"}","old_value":null,"owner":"securitySolution"},"coreMigrationVersion":"8.0.0","id":"9710c840-06a6-11ec-b3f9-3d05c48a7d46","migrationVersion":{"cases-user-actions":"7.14.0"},"references":[{"id":"85541260-06a6-11ec-b3f9-3d05c48a7d46","name":"associated-cases","type":"cases"},{"id":"9687c220-06a6-11ec-b3f9-3d05c48a7d46","name":"associated-cases-comments","type":"cases-comments"}],"score":null,"sort":[1630007310151,9542],"type":"cases-user-actions","updated_at":"2021-08-26T19:48:31.044Z","version":"WzM1MTIsMV0="}
{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":4,"missingRefCount":0,"missingReferences":[]}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{"attributes":{"actionTypeId":".jira","config":{"apiUrl":"https://cases-testing.atlassian.net","projectKey":"TPN"},"isMissingSecrets":true,"name":"A jira connector"},"coreMigrationVersion":"8.0.0","id":"1cd34740-06ad-11ec-babc-0b08808e8e01","migrationVersion":{"action":"7.14.0"},"references":[],"type":"action","updated_at":"2021-08-26T20:35:12.447Z","version":"WzM1ODQsMV0="}
{"attributes":{"closed_at":null,"closed_by":null,"connector":{"fields":[],"name":"none","type":".none"},"created_at":"2021-08-26T20:35:42.131Z","created_by":{"email":null,"full_name":null,"username":"elastic"},"description":"super description","external_service":{"connector_name":"A jira connector","external_id":"10125","external_title":"TPN-118","external_url":"https://cases-testing.atlassian.net/browse/TPN-118","pushed_at":"2021-08-26T20:35:44.302Z","pushed_by":{"email":null,"full_name":null,"username":"elastic"}},"owner":"securitySolution","settings":{"syncAlerts":true},"status":"open","tags":["other tags"],"title":"A case with a connector","type":"individual","updated_at":"2021-08-26T20:36:35.536Z","updated_by":{"email":null,"full_name":null,"username":"elastic"}},"coreMigrationVersion":"8.0.0","id":"2e85c3f0-06ad-11ec-babc-0b08808e8e01","migrationVersion":{"cases":"7.15.0"},"references":[{"id":"1cd34740-06ad-11ec-babc-0b08808e8e01","name":"pushConnectorId","type":"action"}],"type":"cases","updated_at":"2021-08-26T20:36:35.537Z","version":"WzM1OTIsMV0="}
{"attributes":{"action":"create","action_at":"2021-08-26T20:35:42.131Z","action_by":{"email":null,"full_name":null,"username":"elastic"},"action_field":["description","status","tags","title","connector","settings","owner"],"new_value":"{\"type\":\"individual\",\"title\":\"A case with a connector\",\"tags\":[\"other tags\"],\"description\":\"super description\",\"connector\":{\"id\":\"1cd34740-06ad-11ec-babc-0b08808e8e01\",\"name\":\"A jira connector\",\"type\":\".jira\",\"fields\":{\"issueType\":\"10002\",\"parent\":null,\"priority\":\"High\"}},\"settings\":{\"syncAlerts\":true},\"owner\":\"securitySolution\"}","old_value":null,"owner":"securitySolution"},"coreMigrationVersion":"8.0.0","id":"2e9db8c0-06ad-11ec-babc-0b08808e8e01","migrationVersion":{"cases-user-actions":"7.14.0"},"references":[{"id":"2e85c3f0-06ad-11ec-babc-0b08808e8e01","name":"associated-cases","type":"cases"}],"score":null,"sort":[1630010142131,4024],"type":"cases-user-actions","updated_at":"2021-08-26T20:35:42.284Z","version":"WzM1ODksMV0="}
{"attributes":{"action":"push-to-service","action_at":"2021-08-26T20:35:44.302Z","action_by":{"email":null,"full_name":null,"username":"elastic"},"action_field":["pushed"],"new_value":"{\"pushed_at\":\"2021-08-26T20:35:44.302Z\",\"pushed_by\":{\"username\":\"elastic\",\"full_name\":null,\"email\":null},\"connector_id\":\"1cd34740-06ad-11ec-babc-0b08808e8e01\",\"connector_name\":\"A jira connector\",\"external_id\":\"10125\",\"external_title\":\"TPN-118\",\"external_url\":\"https://cases-testing.atlassian.net/browse/TPN-118\"}","old_value":null,"owner":"securitySolution"},"coreMigrationVersion":"8.0.0","id":"2fd1cbf0-06ad-11ec-babc-0b08808e8e01","migrationVersion":{"cases-user-actions":"7.14.0"},"references":[{"id":"2e85c3f0-06ad-11ec-babc-0b08808e8e01","name":"associated-cases","type":"cases"}],"score":null,"sort":[1630010144302,4029],"type":"cases-user-actions","updated_at":"2021-08-26T20:35:44.303Z","version":"WzM1OTAsMV0="}
{"attributes":{"action":"update","action_at":"2021-08-26T20:36:35.536Z","action_by":{"email":null,"full_name":null,"username":"elastic"},"action_field":["connector"],"new_value":"{\"id\":\"none\",\"name\":\"none\",\"type\":\".none\",\"fields\":null}","old_value":"{\"id\":\"1cd34740-06ad-11ec-babc-0b08808e8e01\",\"name\":\"A jira connector\",\"type\":\".jira\",\"fields\":{\"issueType\":\"10002\",\"parent\":null,\"priority\":\"High\"}}","owner":"securitySolution"},"coreMigrationVersion":"8.0.0","id":"4ee9b250-06ad-11ec-babc-0b08808e8e01","migrationVersion":{"cases-user-actions":"7.14.0"},"references":[{"id":"2e85c3f0-06ad-11ec-babc-0b08808e8e01","name":"associated-cases","type":"cases"}],"score":null,"sort":[1630010195536,4033],"type":"cases-user-actions","updated_at":"2021-08-26T20:36:36.469Z","version":"WzM1OTMsMV0="}
{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":5,"missingRefCount":0,"missingReferences":[]}
Loading

0 comments on commit 2567c4f

Please sign in to comment.