Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Cases] Adding feature flag for sub cases #95122

Merged
merged 9 commits into from
Mar 25, 2021
5 changes: 5 additions & 0 deletions x-pack/plugins/cases/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,8 @@ export const SUPPORTED_CONNECTORS = [

export const MAX_ALERTS_PER_SUB_CASE = 5000;
export const MAX_GENERATED_ALERTS_PER_SUB_CASE = MAX_ALERTS_PER_SUB_CASE / DEFAULT_MAX_SIGNALS;

/**
* This flag governs enabling the case as a connector feature. It is disabled by default as the feature is not complete.
*/
export const ENABLE_SUB_CASES = false;
13 changes: 12 additions & 1 deletion x-pack/plugins/cases/server/client/cases/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
CaseUserActionServiceSetup,
} from '../../services';
import { createCaseError } from '../../common/error';
import { ENABLE_SUB_CASES } from '../../../common/constants';

interface CreateCaseArgs {
caseConfigureService: CaseConfigureServiceSetup;
Expand All @@ -60,9 +61,19 @@ export const create = async ({
}: CreateCaseArgs): Promise<CaseResponse> => {
// default to an individual case if the type is not defined.
const { type = CaseType.individual, ...nonTypeCaseFields } = theCase;

if (!ENABLE_SUB_CASES && type === CaseType.collection) {
throw Boom.badRequest(
'Case type cannot be collection when the case connector feature is disabled'
);
}

const query = pipe(
// decode with the defaulted type field
excess(CasesClientPostRequestRt).decode({ type, ...nonTypeCaseFields }),
excess(CasesClientPostRequestRt).decode({
type,
...nonTypeCaseFields,
}),
fold(throwErrors(Boom.badRequest), identity)
);

Expand Down
32 changes: 22 additions & 10 deletions x-pack/plugins/cases/server/client/cases/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
* 2.0.
*/

import { SavedObjectsClientContract, Logger } from 'kibana/server';
import { SavedObjectsClientContract, Logger, SavedObject } from 'kibana/server';
import { flattenCaseSavedObject } from '../../routes/api/utils';
import { CaseResponseRt, CaseResponse } from '../../../common/api';
import { CaseResponseRt, CaseResponse, ESCaseAttributes } from '../../../common/api';
import { CaseServiceSetup } from '../../services';
import { countAlertsForID } from '../../common';
import { createCaseError } from '../../common/error';
import { ENABLE_SUB_CASES } from '../../../common/constants';

interface GetParams {
savedObjectsClient: SavedObjectsClientContract;
Expand All @@ -33,15 +34,26 @@ export const get = async ({
includeSubCaseComments = false,
}: GetParams): Promise<CaseResponse> => {
try {
const [theCase, subCasesForCaseId] = await Promise.all([
caseService.getCase({
let theCase: SavedObject<ESCaseAttributes>;
let subCaseIds: string[] = [];

if (ENABLE_SUB_CASES) {
const [caseInfo, subCasesForCaseId] = await Promise.all([
caseService.getCase({
client: savedObjectsClient,
id,
}),
caseService.findSubCasesByCaseId({ client: savedObjectsClient, ids: [id] }),
]);

theCase = caseInfo;
subCaseIds = subCasesForCaseId.saved_objects.map((so) => so.id);
} else {
theCase = await caseService.getCase({
client: savedObjectsClient,
id,
}),
caseService.findSubCasesByCaseId({ client: savedObjectsClient, ids: [id] }),
]);

const subCaseIds = subCasesForCaseId.saved_objects.map((so) => so.id);
});
}

if (!includeComments) {
return CaseResponseRt.encode(
Expand All @@ -58,7 +70,7 @@ export const get = async ({
sortField: 'created_at',
sortOrder: 'asc',
},
includeSubCaseComments,
includeSubCaseComments: ENABLE_SUB_CASES && includeSubCaseComments,
});

return CaseResponseRt.encode(
Expand Down
9 changes: 7 additions & 2 deletions x-pack/plugins/cases/server/client/cases/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
} from '../../services';
import { CasesClientHandler } from '../client';
import { createCaseError } from '../../common/error';
import { ENABLE_SUB_CASES } from '../../../common/constants';

/**
* Returns true if the case should be closed based on the configuration settings and whether the case
Expand Down Expand Up @@ -92,7 +93,11 @@ export const push = async ({

try {
[theCase, connector, userActions] = await Promise.all([
casesClient.get({ id: caseId, includeComments: true, includeSubCaseComments: true }),
casesClient.get({
id: caseId,
includeComments: true,
includeSubCaseComments: ENABLE_SUB_CASES,
}),
actionsClient.get({ id: connectorId }),
casesClient.getUserActions({ caseId }),
]);
Expand Down Expand Up @@ -183,7 +188,7 @@ export const push = async ({
page: 1,
perPage: theCase?.totalComment ?? 0,
},
includeSubCaseComments: true,
includeSubCaseComments: ENABLE_SUB_CASES,
}),
]);
} catch (e) {
Expand Down
21 changes: 21 additions & 0 deletions x-pack/plugins/cases/server/client/cases/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { CasesClientHandler } from '..';
import { createAlertUpdateRequest } from '../../common';
import { UpdateAlertRequest } from '../types';
import { createCaseError } from '../../common/error';
import { ENABLE_SUB_CASES } from '../../../common/constants';

/**
* Throws an error if any of the requests attempt to update a collection style cases' status field.
Expand Down Expand Up @@ -97,6 +98,22 @@ function throwIfUpdateTypeCollectionToIndividual(
}
}

/**
* Throws an error if any of the requests attempt to update the type of a case.
*/
function throwIfUpdateType(requests: ESCasePatchRequest[]) {
jonathan-buttner marked this conversation as resolved.
Show resolved Hide resolved
const requestsUpdatingType = requests.filter((req) => req.type !== undefined);

if (requestsUpdatingType.length > 0) {
const ids = requestsUpdatingType.map((req) => req.id);
throw Boom.badRequest(
`Updating the type of a case when sub cases are disabled is not allowed ids: [${ids.join(
', '
)}]`
);
}
}

/**
* Throws an error if any of the requests attempt to update an individual style cases' type field to a collection
* when alerts are attached to the case.
Expand Down Expand Up @@ -396,6 +413,10 @@ export const update = async ({
return acc;
}, new Map<string, SavedObject<ESCaseAttributes>>());

if (!ENABLE_SUB_CASES) {
throwIfUpdateType(updateFilterCases);
}

throwIfUpdateStatusOfCollection(updateFilterCases, casesMap);
throwIfUpdateTypeCollectionToIndividual(updateFilterCases, casesMap);
await throwIfInvalidUpdateOfTypeWithAlerts({
Expand Down
20 changes: 15 additions & 5 deletions x-pack/plugins/cases/server/client/comments/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { CommentableCase, createAlertUpdateRequest } from '../../common';
import { CasesClientHandler } from '..';
import { createCaseError } from '../../common/error';
import { CASE_COMMENT_SAVED_OBJECT } from '../../saved_object_types';
import { MAX_GENERATED_ALERTS_PER_SUB_CASE } from '../../../common/constants';
import { ENABLE_SUB_CASES, MAX_GENERATED_ALERTS_PER_SUB_CASE } from '../../../common/constants';

async function getSubCase({
caseService,
Expand Down Expand Up @@ -224,10 +224,14 @@ async function getCombinedCase({
client,
id,
}),
service.getSubCase({
client,
id,
}),
...(ENABLE_SUB_CASES
? [
service.getSubCase({
client,
id,
}),
]
: [Promise.reject('case connector feature is disabled')]),
]);

if (subCasePromise.status === 'fulfilled') {
Expand Down Expand Up @@ -287,6 +291,12 @@ export const addComment = async ({
);

if (isCommentRequestTypeGenAlert(comment)) {
if (!ENABLE_SUB_CASES) {
throw Boom.badRequest(
'Attempting to add a generated alert when case connector feature is disabled'
);
}

return addGeneratedAlerts({
caseId,
comment,
Expand Down
29 changes: 28 additions & 1 deletion x-pack/plugins/cases/server/connectors/case/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -886,7 +886,34 @@ describe('case connector', () => {
});
});

describe('execute', () => {
it('should throw an error when executing the connector', async () => {
expect.assertions(2);
const actionId = 'some-id';
const params: CaseExecutorParams = {
// @ts-expect-error
subAction: 'not-supported',
// @ts-expect-error
subActionParams: {},
};

const executorOptions: CaseActionTypeExecutorOptions = {
actionId,
config: {},
params,
secrets: {},
services,
};

try {
await caseActionType.executor(executorOptions);
} catch (e) {
expect(e).not.toBeNull();
expect(e.message).toBe('[Action][Case] connector not supported');
}
});

// ENABLE_SUB_CASES: enable these tests after the case connector feature is completed
describe.skip('execute', () => {
it('allows only supported sub-actions', async () => {
expect.assertions(2);
const actionId = 'some-id';
Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/cases/server/connectors/case/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import * as i18n from './translations';
import { GetActionTypeParams, isCommentGeneratedAlert, separator } from '..';
import { nullUser } from '../../common';
import { createCaseError } from '../../common/error';
import { ENABLE_SUB_CASES } from '../../../common/constants';

const supportedSubActions: string[] = ['create', 'update', 'addComment'];

Expand Down Expand Up @@ -70,6 +71,12 @@ async function executor(
}: GetActionTypeParams,
execOptions: CaseActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult<CaseExecutorResponse | {}>> {
if (!ENABLE_SUB_CASES) {
const msg = '[Action][Case] connector not supported';
logger.error(msg);
throw new Error(msg);
}

const { actionId, params, services } = execOptions;
const { subAction, subActionParams } = params;
let data: CaseExecutorResponse | null = null;
Expand Down
29 changes: 16 additions & 13 deletions x-pack/plugins/cases/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { CoreSetup, CoreStart } from 'src/core/server';

import { SecurityPluginSetup } from '../../security/server';
import { PluginSetupContract as ActionsPluginSetup } from '../../actions/server';
import { APP_ID } from '../common/constants';
import { APP_ID, ENABLE_SUB_CASES } from '../common/constants';

import { ConfigType } from './config';
import { initCaseApi } from './routes/api';
Expand Down Expand Up @@ -54,23 +54,23 @@ export class CasePlugin {
private connectorMappingsService?: ConnectorMappingsServiceSetup;
private userActionService?: CaseUserActionServiceSetup;
private alertsService?: AlertService;
private config?: ConfigType;

constructor(private readonly initializerContext: PluginInitializerContext) {
this.log = this.initializerContext.logger.get();
}

public async setup(core: CoreSetup, plugins: PluginsSetup) {
const config = createConfig(this.initializerContext);
this.config = createConfig(this.initializerContext);

if (!config.enabled) {
if (!this.config.enabled) {
return;
}

core.savedObjects.registerType(caseCommentSavedObjectType);
core.savedObjects.registerType(caseConfigureSavedObjectType);
core.savedObjects.registerType(caseConnectorMappingsSavedObjectType);
core.savedObjects.registerType(caseSavedObjectType);
core.savedObjects.registerType(subCaseSavedObjectType);
core.savedObjects.registerType(caseUserActionSavedObjectType);

this.log.debug(
Expand Down Expand Up @@ -111,15 +111,18 @@ export class CasePlugin {
router,
});

registerConnectors({
actionsRegisterType: plugins.actions.registerType,
logger: this.log,
caseService: this.caseService,
caseConfigureService: this.caseConfigureService,
connectorMappingsService: this.connectorMappingsService,
userActionService: this.userActionService,
alertsService: this.alertsService,
});
if (ENABLE_SUB_CASES) {
core.savedObjects.registerType(subCaseSavedObjectType);
registerConnectors({
actionsRegisterType: plugins.actions.registerType,
logger: this.log,
caseService: this.caseService,
caseConfigureService: this.caseConfigureService,
connectorMappingsService: this.connectorMappingsService,
userActionService: this.userActionService,
alertsService: this.alertsService,
});
}
}

public start(core: CoreStart) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
* 2.0.
*/

import Boom from '@hapi/boom';
import { schema } from '@kbn/config-schema';

import { buildCommentUserActionItem } from '../../../../services/user_actions/helpers';
import { RouteDeps } from '../../types';
import { wrapError } from '../../utils';
import { CASE_COMMENTS_URL } from '../../../../../common/constants';
import { CASE_COMMENTS_URL, ENABLE_SUB_CASES } from '../../../../../common/constants';
import { AssociationType } from '../../../../../common/api';

export function initDeleteAllCommentsApi({
Expand All @@ -35,18 +35,23 @@ export function initDeleteAllCommentsApi({
},
async (context, request, response) => {
try {
if (!ENABLE_SUB_CASES && request.query?.subCaseId !== undefined) {
throw Boom.badRequest(
'The `subCaseId` is not supported when the case connector feature is disabled'
);
}

const client = context.core.savedObjects.client;
// eslint-disable-next-line @typescript-eslint/naming-convention
const { username, full_name, email } = await caseService.getUser({ request });
const deleteDate = new Date().toISOString();

const id = request.query?.subCaseId ?? request.params.case_id;
const subCaseId = request.query?.subCaseId;
const id = subCaseId ?? request.params.case_id;
const comments = await caseService.getCommentsByAssociation({
client,
id,
associationType: request.query?.subCaseId
? AssociationType.subCase
: AssociationType.case,
associationType: subCaseId ? AssociationType.subCase : AssociationType.case,
});

await Promise.all(
Expand All @@ -66,7 +71,7 @@ export function initDeleteAllCommentsApi({
actionAt: deleteDate,
actionBy: { username, full_name, email },
caseId: request.params.case_id,
subCaseId: request.query?.subCaseId,
subCaseId,
commentId: comment.id,
fields: ['comment'],
})
Expand Down
Loading