(
diff --git a/x-pack/plugins/case/server/client/cases/types.ts b/x-pack/plugins/case/server/client/cases/types.ts
index 2dd2caf9fe73a..f1d56e7132bd1 100644
--- a/x-pack/plugins/case/server/client/cases/types.ts
+++ b/x-pack/plugins/case/server/client/cases/types.ts
@@ -72,7 +72,7 @@ export interface TransformFieldsArgs {
export interface ExternalServiceComment {
comment: string;
- commentId?: string;
+ commentId: string;
}
export interface MapIncident {
diff --git a/x-pack/plugins/case/server/client/cases/utils.test.ts b/x-pack/plugins/case/server/client/cases/utils.test.ts
index 44e7a682aa7ed..859114a5e8fb0 100644
--- a/x-pack/plugins/case/server/client/cases/utils.test.ts
+++ b/x-pack/plugins/case/server/client/cases/utils.test.ts
@@ -540,6 +540,7 @@ describe('utils', () => {
},
{
comment: 'Elastic Security Alerts attached to the case: 3',
+ commentId: 'mock-id-1-total-alerts',
},
]);
});
@@ -569,6 +570,7 @@ describe('utils', () => {
},
{
comment: 'Elastic Security Alerts attached to the case: 4',
+ commentId: 'mock-id-1-total-alerts',
},
]);
});
diff --git a/x-pack/plugins/case/server/client/cases/utils.ts b/x-pack/plugins/case/server/client/cases/utils.ts
index a5013d9b93982..67d5ef55f83c3 100644
--- a/x-pack/plugins/case/server/client/cases/utils.ts
+++ b/x-pack/plugins/case/server/client/cases/utils.ts
@@ -185,6 +185,7 @@ export const createIncident = async ({
if (totalAlerts > 0) {
comments.push({
comment: `Elastic Security Alerts attached to the case: ${totalAlerts}`,
+ commentId: `${theCase.id}-total-alerts`,
});
}
diff --git a/x-pack/plugins/case/server/client/comments/add.ts b/x-pack/plugins/case/server/client/comments/add.ts
index 0a86c1825fedc..4c1cc59a95750 100644
--- a/x-pack/plugins/case/server/client/comments/add.ts
+++ b/x-pack/plugins/case/server/client/comments/add.ts
@@ -25,7 +25,7 @@ import {
CaseType,
SubCaseAttributes,
CommentRequest,
- CollectionWithSubCaseResponse,
+ CaseResponse,
User,
CommentRequestAlertType,
AlertCommentRequestRt,
@@ -113,7 +113,7 @@ const addGeneratedAlerts = async ({
caseClient,
caseId,
comment,
-}: AddCommentFromRuleArgs): Promise => {
+}: AddCommentFromRuleArgs): Promise => {
const query = pipe(
AlertCommentRequestRt.decode(comment),
fold(throwErrors(Boom.badRequest), identity)
@@ -260,7 +260,7 @@ export const addComment = async ({
caseId,
comment,
user,
-}: AddCommentArgs): Promise => {
+}: AddCommentArgs): Promise => {
const query = pipe(
CommentRequestRt.decode(comment),
fold(throwErrors(Boom.badRequest), identity)
diff --git a/x-pack/plugins/case/server/client/types.ts b/x-pack/plugins/case/server/client/types.ts
index ba5677426c222..d6a8f6b5d706c 100644
--- a/x-pack/plugins/case/server/client/types.ts
+++ b/x-pack/plugins/case/server/client/types.ts
@@ -13,7 +13,6 @@ import {
CasesPatchRequest,
CasesResponse,
CaseStatuses,
- CollectionWithSubCaseResponse,
CommentRequest,
ConnectorMappingsAttributes,
GetFieldsResponse,
@@ -89,7 +88,7 @@ export interface ConfigureFields {
* This represents the interface that other plugins can access.
*/
export interface CaseClient {
- addComment(args: CaseClientAddComment): Promise;
+ addComment(args: CaseClientAddComment): Promise;
create(theCase: CasePostRequest): Promise;
get(args: CaseClientGet): Promise;
getAlerts(args: CaseClientGetAlerts): Promise;
diff --git a/x-pack/plugins/case/server/common/models/commentable_case.ts b/x-pack/plugins/case/server/common/models/commentable_case.ts
index 9827118ee8e29..3ae225999db4e 100644
--- a/x-pack/plugins/case/server/common/models/commentable_case.ts
+++ b/x-pack/plugins/case/server/common/models/commentable_case.ts
@@ -17,8 +17,8 @@ import {
CaseSettings,
CaseStatuses,
CaseType,
- CollectionWithSubCaseResponse,
- CollectWithSubCaseResponseRt,
+ CaseResponse,
+ CaseResponseRt,
CommentAttributes,
CommentPatchRequest,
CommentRequest,
@@ -254,7 +254,7 @@ export class CommentableCase {
};
}
- public async encode(): Promise {
+ public async encode(): Promise {
const collectionCommentStats = await this.service.getAllCaseComments({
client: this.soClient,
id: this.collection.id,
@@ -265,22 +265,6 @@ export class CommentableCase {
},
});
- if (this.subCase) {
- const subCaseComments = await this.service.getAllSubCaseComments({
- client: this.soClient,
- id: this.subCase.id,
- });
-
- return CollectWithSubCaseResponseRt.encode({
- subCase: flattenSubCaseSavedObject({
- savedObject: this.subCase,
- comments: subCaseComments.saved_objects,
- totalAlerts: countAlertsForID({ comments: subCaseComments, id: this.subCase.id }),
- }),
- ...this.formatCollectionForEncoding(collectionCommentStats.total),
- });
- }
-
const collectionComments = await this.service.getAllCaseComments({
client: this.soClient,
id: this.collection.id,
@@ -291,10 +275,45 @@ export class CommentableCase {
},
});
- return CollectWithSubCaseResponseRt.encode({
+ const collectionTotalAlerts =
+ countAlertsForID({ comments: collectionComments, id: this.collection.id }) ?? 0;
+
+ const caseResponse = {
comments: flattenCommentSavedObjects(collectionComments.saved_objects),
- totalAlerts: countAlertsForID({ comments: collectionComments, id: this.collection.id }),
+ totalAlerts: collectionTotalAlerts,
...this.formatCollectionForEncoding(collectionCommentStats.total),
- });
+ };
+
+ if (this.subCase) {
+ const subCaseComments = await this.service.getAllSubCaseComments({
+ client: this.soClient,
+ id: this.subCase.id,
+ });
+ const totalAlerts = countAlertsForID({ comments: subCaseComments, id: this.subCase.id }) ?? 0;
+
+ return CaseResponseRt.encode({
+ ...caseResponse,
+ /**
+ * For now we need the sub case comments and totals to be exposed on the top level of the response so that the UI
+ * functionality can stay the same. Ideally in the future we can refactor this so that the UI will look for the
+ * comments either in the top level for a case or a collection or in the subCases field if it is a sub case.
+ *
+ * If we ever need to return both the collection's comments and the sub case comments we'll need to refactor it then
+ * as well.
+ */
+ comments: flattenCommentSavedObjects(subCaseComments.saved_objects),
+ totalComment: subCaseComments.saved_objects.length,
+ totalAlerts,
+ subCases: [
+ flattenSubCaseSavedObject({
+ savedObject: this.subCase,
+ totalComment: subCaseComments.saved_objects.length,
+ totalAlerts,
+ }),
+ ],
+ });
+ }
+
+ return CaseResponseRt.encode(caseResponse);
}
}
diff --git a/x-pack/plugins/case/server/connectors/case/index.test.ts b/x-pack/plugins/case/server/connectors/case/index.test.ts
index 4be519858db18..e4c29bb099f0e 100644
--- a/x-pack/plugins/case/server/connectors/case/index.test.ts
+++ b/x-pack/plugins/case/server/connectors/case/index.test.ts
@@ -18,7 +18,6 @@ import {
AssociationType,
CaseResponse,
CasesResponse,
- CollectionWithSubCaseResponse,
} from '../../../common/api';
import {
connectorMappingsServiceMock,
@@ -1018,9 +1017,10 @@ describe('case connector', () => {
describe('addComment', () => {
it('executes correctly', async () => {
- const commentReturn: CollectionWithSubCaseResponse = {
+ const commentReturn: CaseResponse = {
id: 'mock-it',
totalComment: 0,
+ totalAlerts: 0,
version: 'WzksMV0=',
closed_at: null,
diff --git a/x-pack/plugins/case/server/connectors/case/types.ts b/x-pack/plugins/case/server/connectors/case/types.ts
index 50ff104d7bad0..6a7dfd9c2e687 100644
--- a/x-pack/plugins/case/server/connectors/case/types.ts
+++ b/x-pack/plugins/case/server/connectors/case/types.ts
@@ -16,7 +16,7 @@ import {
ConnectorSchema,
CommentSchema,
} from './schema';
-import { CaseResponse, CasesResponse, CollectionWithSubCaseResponse } from '../../../common/api';
+import { CaseResponse, CasesResponse } from '../../../common/api';
export type CaseConfiguration = TypeOf;
export type Connector = TypeOf;
@@ -29,7 +29,7 @@ export type ExecutorSubActionAddCommentParams = TypeOf<
>;
export type CaseExecutorParams = TypeOf;
-export type CaseExecutorResponse = CaseResponse | CasesResponse | CollectionWithSubCaseResponse;
+export type CaseExecutorResponse = CaseResponse | CasesResponse;
export type CaseActionType = ActionType<
CaseConfiguration,
diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/delete_all_comments.ts b/x-pack/plugins/case/server/routes/api/cases/comments/delete_all_comments.ts
index bcbf1828e1fde..e0b3a4420f4b5 100644
--- a/x-pack/plugins/case/server/routes/api/cases/comments/delete_all_comments.ts
+++ b/x-pack/plugins/case/server/routes/api/cases/comments/delete_all_comments.ts
@@ -23,7 +23,7 @@ export function initDeleteAllCommentsApi({ caseService, router, userActionServic
}),
query: schema.maybe(
schema.object({
- subCaseID: schema.maybe(schema.string()),
+ subCaseId: schema.maybe(schema.string()),
})
),
},
@@ -35,11 +35,11 @@ export function initDeleteAllCommentsApi({ caseService, router, userActionServic
const { username, full_name, email } = await caseService.getUser({ request });
const deleteDate = new Date().toISOString();
- const id = request.query?.subCaseID ?? request.params.case_id;
+ const id = request.query?.subCaseId ?? request.params.case_id;
const comments = await caseService.getCommentsByAssociation({
client,
id,
- associationType: request.query?.subCaseID
+ associationType: request.query?.subCaseId
? AssociationType.subCase
: AssociationType.case,
});
@@ -61,7 +61,7 @@ export function initDeleteAllCommentsApi({ caseService, router, userActionServic
actionAt: deleteDate,
actionBy: { username, full_name, email },
caseId: request.params.case_id,
- subCaseId: request.query?.subCaseID,
+ subCaseId: request.query?.subCaseId,
commentId: comment.id,
fields: ['comment'],
})
diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.ts b/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.ts
index 73307753a550d..cae0809ea5f0b 100644
--- a/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.ts
+++ b/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.ts
@@ -25,7 +25,7 @@ export function initDeleteCommentApi({ caseService, router, userActionService }:
}),
query: schema.maybe(
schema.object({
- subCaseID: schema.maybe(schema.string()),
+ subCaseId: schema.maybe(schema.string()),
})
),
},
@@ -46,8 +46,8 @@ export function initDeleteCommentApi({ caseService, router, userActionService }:
throw Boom.notFound(`This comment ${request.params.comment_id} does not exist anymore.`);
}
- const type = request.query?.subCaseID ? SUB_CASE_SAVED_OBJECT : CASE_SAVED_OBJECT;
- const id = request.query?.subCaseID ?? request.params.case_id;
+ const type = request.query?.subCaseId ? SUB_CASE_SAVED_OBJECT : CASE_SAVED_OBJECT;
+ const id = request.query?.subCaseId ?? request.params.case_id;
const caseRef = myComment.references.find((c) => c.type === type);
if (caseRef == null || (caseRef != null && caseRef.id !== id)) {
@@ -69,7 +69,7 @@ export function initDeleteCommentApi({ caseService, router, userActionService }:
actionAt: deleteDate,
actionBy: { username, full_name, email },
caseId: id,
- subCaseId: request.query?.subCaseID,
+ subCaseId: request.query?.subCaseId,
commentId: request.params.comment_id,
fields: ['comment'],
}),
diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/find_comments.ts b/x-pack/plugins/case/server/routes/api/cases/comments/find_comments.ts
index 3431c340c791e..0ec0f1871c7ad 100644
--- a/x-pack/plugins/case/server/routes/api/cases/comments/find_comments.ts
+++ b/x-pack/plugins/case/server/routes/api/cases/comments/find_comments.ts
@@ -27,7 +27,7 @@ import { defaultPage, defaultPerPage } from '../..';
const FindQueryParamsRt = rt.partial({
...SavedObjectFindOptionsRt.props,
- subCaseID: rt.string,
+ subCaseId: rt.string,
});
export function initFindCaseCommentsApi({ caseService, router }: RouteDeps) {
@@ -49,8 +49,8 @@ export function initFindCaseCommentsApi({ caseService, router }: RouteDeps) {
fold(throwErrors(Boom.badRequest), identity)
);
- const id = query.subCaseID ?? request.params.case_id;
- const associationType = query.subCaseID ? AssociationType.subCase : AssociationType.case;
+ const id = query.subCaseId ?? request.params.case_id;
+ const associationType = query.subCaseId ? AssociationType.subCase : AssociationType.case;
const args = query
? {
caseService,
diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/get_all_comment.ts b/x-pack/plugins/case/server/routes/api/cases/comments/get_all_comment.ts
index 730b1b92a8a07..8bf49ec3e27a1 100644
--- a/x-pack/plugins/case/server/routes/api/cases/comments/get_all_comment.ts
+++ b/x-pack/plugins/case/server/routes/api/cases/comments/get_all_comment.ts
@@ -25,7 +25,7 @@ export function initGetAllCommentsApi({ caseService, router }: RouteDeps) {
query: schema.maybe(
schema.object({
includeSubCaseComments: schema.maybe(schema.boolean()),
- subCaseID: schema.maybe(schema.string()),
+ subCaseId: schema.maybe(schema.string()),
})
),
},
@@ -35,10 +35,10 @@ export function initGetAllCommentsApi({ caseService, router }: RouteDeps) {
const client = context.core.savedObjects.client;
let comments: SavedObjectsFindResponse;
- if (request.query?.subCaseID) {
+ if (request.query?.subCaseId) {
comments = await caseService.getAllSubCaseComments({
client,
- id: request.query.subCaseID,
+ id: request.query.subCaseId,
options: {
sortField: defaultSortField,
},
diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts
index e8b6f7bc957eb..01b0e17464053 100644
--- a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts
+++ b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts
@@ -26,11 +26,11 @@ interface CombinedCaseParams {
service: CaseServiceSetup;
client: SavedObjectsClientContract;
caseID: string;
- subCaseID?: string;
+ subCaseId?: string;
}
-async function getCommentableCase({ service, client, caseID, subCaseID }: CombinedCaseParams) {
- if (subCaseID) {
+async function getCommentableCase({ service, client, caseID, subCaseId }: CombinedCaseParams) {
+ if (subCaseId) {
const [caseInfo, subCase] = await Promise.all([
service.getCase({
client,
@@ -38,7 +38,7 @@ async function getCommentableCase({ service, client, caseID, subCaseID }: Combin
}),
service.getSubCase({
client,
- id: subCaseID,
+ id: subCaseId,
}),
]);
return new CommentableCase({ collection: caseInfo, service, subCase, soClient: client });
@@ -66,7 +66,7 @@ export function initPatchCommentApi({
}),
query: schema.maybe(
schema.object({
- subCaseID: schema.maybe(schema.string()),
+ subCaseId: schema.maybe(schema.string()),
})
),
body: escapeHatch,
@@ -87,7 +87,7 @@ export function initPatchCommentApi({
service: caseService,
client,
caseID: request.params.case_id,
- subCaseID: request.query?.subCaseID,
+ subCaseId: request.query?.subCaseId,
});
const myComment = await caseService.getComment({
@@ -103,7 +103,7 @@ export function initPatchCommentApi({
throw Boom.badRequest(`You cannot change the type of the comment.`);
}
- const saveObjType = request.query?.subCaseID ? SUB_CASE_SAVED_OBJECT : CASE_SAVED_OBJECT;
+ const saveObjType = request.query?.subCaseId ? SUB_CASE_SAVED_OBJECT : CASE_SAVED_OBJECT;
const caseRef = myComment.references.find((c) => c.type === saveObjType);
if (caseRef == null || (caseRef != null && caseRef.id !== commentableCase.id)) {
@@ -144,7 +144,7 @@ export function initPatchCommentApi({
actionAt: updatedDate,
actionBy: { username, full_name, email },
caseId: request.params.case_id,
- subCaseId: request.query?.subCaseID,
+ subCaseId: request.query?.subCaseId,
commentId: updatedComment.id,
fields: ['comment'],
newValue: JSON.stringify(queryRestAttributes),
diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts
index 95b611950bd41..607f3f381f067 100644
--- a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts
+++ b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts
@@ -21,7 +21,7 @@ export function initPostCommentApi({ router }: RouteDeps) {
}),
query: schema.maybe(
schema.object({
- subCaseID: schema.maybe(schema.string()),
+ subCaseId: schema.maybe(schema.string()),
})
),
body: escapeHatch,
@@ -33,7 +33,7 @@ export function initPostCommentApi({ router }: RouteDeps) {
}
const caseClient = context.case.getCaseClient();
- const caseId = request.query?.subCaseID ?? request.params.case_id;
+ const caseId = request.query?.subCaseId ?? request.params.case_id;
const comment = request.body as CommentRequest;
try {
diff --git a/x-pack/plugins/case/server/routes/api/cases/sub_case/patch_sub_cases.ts b/x-pack/plugins/case/server/routes/api/cases/sub_case/patch_sub_cases.ts
index ca5cd657a39f3..4b8e4920852c2 100644
--- a/x-pack/plugins/case/server/routes/api/cases/sub_case/patch_sub_cases.ts
+++ b/x-pack/plugins/case/server/routes/api/cases/sub_case/patch_sub_cases.ts
@@ -153,8 +153,8 @@ async function getParentCases({
return parentCases.saved_objects.reduce((acc, so) => {
const subCaseIDsWithParent = parentIDInfo.parentIDToSubID.get(so.id);
- subCaseIDsWithParent?.forEach((subCaseID) => {
- acc.set(subCaseID, so);
+ subCaseIDsWithParent?.forEach((subCaseId) => {
+ acc.set(subCaseId, so);
});
return acc;
}, new Map>());
diff --git a/x-pack/plugins/case/server/scripts/sub_cases/index.ts b/x-pack/plugins/case/server/scripts/sub_cases/index.ts
index f6ec1f44076e4..ba3bcaa65091c 100644
--- a/x-pack/plugins/case/server/scripts/sub_cases/index.ts
+++ b/x-pack/plugins/case/server/scripts/sub_cases/index.ts
@@ -8,12 +8,7 @@
import yargs from 'yargs';
import { ToolingLog } from '@kbn/dev-utils';
import { KbnClient } from '@kbn/test';
-import {
- CaseResponse,
- CaseType,
- CollectionWithSubCaseResponse,
- ConnectorTypes,
-} from '../../../common/api';
+import { CaseResponse, CaseType, ConnectorTypes } from '../../../common/api';
import { CommentType } from '../../../common/api/cases/comment';
import { CASES_URL } from '../../../common/constants';
import { ActionResult, ActionTypeExecutorResult } from '../../../../actions/common';
@@ -119,9 +114,7 @@ async function handleGenGroupAlerts(argv: any) {
),
};
- const executeResp = await client.request<
- ActionTypeExecutorResult
- >({
+ const executeResp = await client.request>({
path: `/api/actions/action/${createdAction.data.id}/_execute`,
method: 'POST',
body: {
diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx
index e1427bc96e7e0..c69c7ebff2b9e 100644
--- a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx
+++ b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx
@@ -66,7 +66,7 @@ export class LogStreamEmbeddable extends Embeddable {
}
const startTimestamp = datemathToEpochMillis(this.input.timeRange.from);
- const endTimestamp = datemathToEpochMillis(this.input.timeRange.to);
+ const endTimestamp = datemathToEpochMillis(this.input.timeRange.to, 'up');
if (!startTimestamp || !endTimestamp) {
return;
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js
index 02342c895f077..323d267899bdf 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js
@@ -7,8 +7,6 @@
import { cloneDeep, get, pick } from 'lodash';
-import { WEEK } from '../../../../../../../../src/plugins/es_ui_shared/public';
-
import { validateId } from './validate_id';
import { validateIndexPattern } from './validate_index_pattern';
import { validateRollupIndex } from './validate_rollup_index';
@@ -66,7 +64,7 @@ export const stepIdToStepConfigMap = {
// a few hours as they're being restarted. A delay of 1d would allow them that period to reboot
// and the "expense" is pretty negligible in most cases: 1 day of extra non-rolled-up data.
rollupDelay: '1d',
- cronFrequency: WEEK,
+ cronFrequency: 'WEEK',
fieldToPreferredValueMap: {},
};
diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js
index e4371d1ab07cd..b7341ea21e3b1 100644
--- a/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js
+++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js
@@ -181,6 +181,11 @@ describe('Create Rollup Job, step 1: Logistics', () => {
expect(options).toEqual(['minute', 'hour', 'day', 'week', 'month', 'year']);
});
+ it('should default to "WEEK"', () => {
+ const frequencySelect = find('cronFrequencySelect');
+ expect(frequencySelect.props().value).toBe('WEEK');
+ });
+
describe('every minute', () => {
it('should not have any additional configuration', () => {
changeFrequency('MINUTE');
diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.ts
index 725a2eb9fea7b..79b912e082fdb 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/utils.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/utils.ts
@@ -5,9 +5,19 @@
* 2.0.
*/
-import { EntriesArray } from '../shared_imports';
+import {
+ CreateExceptionListItemSchema,
+ EntriesArray,
+ ExceptionListItemSchema,
+} from '../shared_imports';
import { Type } from './schemas/common/schemas';
+export const hasLargeValueItem = (
+ exceptionItems: Array
+) => {
+ return exceptionItems.some((exceptionItem) => hasLargeValueList(exceptionItem.entries));
+};
+
export const hasLargeValueList = (entries: EntriesArray): boolean => {
const found = entries.filter(({ type }) => type === 'list');
return found.length > 0;
diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/expanded_row.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/expanded_row.tsx
index bb4bd0f98949d..1e1e925a20ada 100644
--- a/x-pack/plugins/security_solution/public/cases/components/all_cases/expanded_row.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/expanded_row.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { EuiBasicTable as _EuiBasicTable } from '@elastic/eui';
import styled from 'styled-components';
-import { Case } from '../../containers/types';
+import { Case, SubCase } from '../../containers/types';
import { CasesColumns } from './columns';
import { AssociationType } from '../../../../../case/common/api';
@@ -34,14 +34,25 @@ BasicTable.displayName = 'BasicTable';
export const getExpandedRowMap = ({
data,
columns,
+ isModal,
+ onSubCaseClick,
}: {
data: Case[] | null;
columns: CasesColumns[];
+ isModal: boolean;
+ onSubCaseClick?: (theSubCase: SubCase) => void;
}): ExpandedRowMap => {
if (data == null) {
return {};
}
+ const rowProps = (theSubCase: SubCase) => {
+ return {
+ ...(isModal && onSubCaseClick ? { onClick: () => onSubCaseClick(theSubCase) } : {}),
+ className: 'subCase',
+ };
+ };
+
return data.reduce((acc, curr) => {
if (curr.subCases != null) {
const subCases = curr.subCases.map((subCase, index) => ({
@@ -58,6 +69,7 @@ export const getExpandedRowMap = ({
data-test-subj={`sub-cases-table-${curr.id}`}
itemId="id"
items={subCases}
+ rowProps={rowProps}
/>
),
};
diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx
index ce0fea07bf473..56dcf3bc28757 100644
--- a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx
@@ -19,11 +19,12 @@ import {
import { EuiTableSelectionType } from '@elastic/eui/src/components/basic_table/table_types';
import { isEmpty, memoize } from 'lodash/fp';
import styled, { css } from 'styled-components';
-import * as i18n from './translations';
+import classnames from 'classnames';
-import { CaseStatuses } from '../../../../../case/common/api';
+import * as i18n from './translations';
+import { CaseStatuses, CaseType } from '../../../../../case/common/api';
import { getCasesColumns } from './columns';
-import { Case, DeleteCase, FilterOptions, SortFieldCase } from '../../containers/types';
+import { Case, DeleteCase, FilterOptions, SortFieldCase, SubCase } from '../../containers/types';
import { useGetCases, UpdateCase } from '../../containers/use_get_cases';
import { useGetCasesStatus } from '../../containers/use_get_cases_status';
import { useDeleteCases } from '../../containers/use_delete_cases';
@@ -58,6 +59,7 @@ import { getExpandedRowMap } from './expanded_row';
const Div = styled.div`
margin-top: ${({ theme }) => theme.eui.paddingSizes.m};
`;
+
const FlexItemDivider = styled(EuiFlexItem)`
${({ theme }) => css`
.euiFlexGroup--gutterMedium > &.euiFlexItem {
@@ -75,6 +77,7 @@ const ProgressLoader = styled(EuiProgress)`
z-index: ${theme.eui.euiZHeader};
`}
`;
+
const getSortField = (field: string): SortFieldCase => {
if (field === SortFieldCase.createdAt) {
return SortFieldCase.createdAt;
@@ -86,19 +89,39 @@ const getSortField = (field: string): SortFieldCase => {
const EuiBasicTable: any = _EuiBasicTable; // eslint-disable-line @typescript-eslint/no-explicit-any
const BasicTable = styled(EuiBasicTable)`
- .euiTableRow-isExpandedRow.euiTableRow-isSelectable .euiTableCellContent {
- padding: 8px 0 8px 32px;
- }
+ ${({ theme }) => `
+ .euiTableRow-isExpandedRow.euiTableRow-isSelectable .euiTableCellContent {
+ padding: 8px 0 8px 32px;
+ }
+
+ &.isModal .euiTableRow.isDisabled {
+ cursor: not-allowed;
+ background-color: ${theme.eui.euiTableHoverClickableColor};
+ }
+
+ &.isModal .euiTableRow.euiTableRow-isExpandedRow .euiTableRowCell,
+ &.isModal .euiTableRow.euiTableRow-isExpandedRow:hover {
+ background-color: transparent;
+ }
+
+ &.isModal .euiTableRow.euiTableRow-isExpandedRow {
+ .subCase:hover {
+ background-color: ${theme.eui.euiTableHoverClickableColor};
+ }
+ }
+ `}
`;
BasicTable.displayName = 'BasicTable';
interface AllCasesProps {
- onRowClick?: (theCase?: Case) => void;
+ onRowClick?: (theCase?: Case | SubCase) => void;
isModal?: boolean;
userCanCrud: boolean;
+ disabledStatuses?: CaseStatuses[];
+ disabledCases?: CaseType[];
}
export const AllCases = React.memo(
- ({ onRowClick, isModal = false, userCanCrud }) => {
+ ({ onRowClick, isModal = false, userCanCrud, disabledStatuses, disabledCases = [] }) => {
const { navigateToApp } = useKibana().services.application;
const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.case);
const { actionLicense } = useGetActionLicense();
@@ -334,8 +357,10 @@ export const AllCases = React.memo(
getExpandedRowMap({
columns: memoizedGetCasesColumns,
data: data.cases,
+ isModal,
+ onSubCaseClick: onRowClick,
}),
- [data.cases, memoizedGetCasesColumns]
+ [data.cases, isModal, memoizedGetCasesColumns, onRowClick]
);
const memoizedPagination = useMemo(
@@ -356,6 +381,7 @@ export const AllCases = React.memo(
() => ({
selectable: (theCase) => isEmpty(theCase.subCases),
onSelectionChange: setSelectedCases,
+ selectableMessage: (selectable) => (!selectable ? i18n.SELECTABLE_MESSAGE_COLLECTIONS : ''),
}),
[setSelectedCases]
);
@@ -377,7 +403,8 @@ export const AllCases = React.memo(
return {
'data-test-subj': `cases-table-row-${theCase.id}`,
- ...(isModal ? { onClick: onTableRowClick } : {}),
+ className: classnames({ isDisabled: theCase.type === CaseType.collection }),
+ ...(isModal && theCase.type !== CaseType.collection ? { onClick: onTableRowClick } : {}),
};
},
[isModal, onRowClick]
@@ -462,6 +489,7 @@ export const AllCases = React.memo(
status: filterOptions.status,
}}
setFilterRefetch={setFilterRefetch}
+ disabledStatuses={disabledStatuses}
/>
{isCasesLoading && isDataEmpty ? (
@@ -530,6 +558,7 @@ export const AllCases = React.memo
(
rowProps={tableRowProps}
selection={userCanCrud && !isModal ? euiBasicTableSelectionProps : undefined}
sorting={sorting}
+ className={classnames({ isModal })}
/>
)}
diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/status_filter.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/status_filter.test.tsx
index 785d4447c0acf..11d53b6609e74 100644
--- a/x-pack/plugins/security_solution/public/cases/components/all_cases/status_filter.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/status_filter.test.tsx
@@ -61,4 +61,24 @@ describe('StatusFilter', () => {
expect(onStatusChanged).toBeCalledWith('closed');
});
});
+
+ it('should disabled selected statuses', () => {
+ const wrapper = mount(
+
+ );
+
+ wrapper.find('button[data-test-subj="case-status-filter"]').simulate('click');
+
+ expect(
+ wrapper.find('button[data-test-subj="case-status-filter-open"]').prop('disabled')
+ ).toBeFalsy();
+
+ expect(
+ wrapper.find('button[data-test-subj="case-status-filter-in-progress"]').prop('disabled')
+ ).toBeFalsy();
+
+ expect(
+ wrapper.find('button[data-test-subj="case-status-filter-closed"]').prop('disabled')
+ ).toBeTruthy();
+ });
});
diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/status_filter.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/status_filter.tsx
index 7fa0625229b48..41997d6f38421 100644
--- a/x-pack/plugins/security_solution/public/cases/components/all_cases/status_filter.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/status_filter.tsx
@@ -14,9 +14,15 @@ interface Props {
stats: Record;
selectedStatus: CaseStatuses;
onStatusChanged: (status: CaseStatuses) => void;
+ disabledStatuses?: CaseStatuses[];
}
-const StatusFilterComponent: React.FC = ({ stats, selectedStatus, onStatusChanged }) => {
+const StatusFilterComponent: React.FC = ({
+ stats,
+ selectedStatus,
+ onStatusChanged,
+ disabledStatuses = [],
+}) => {
const caseStatuses = Object.keys(statuses) as CaseStatuses[];
const options: Array> = caseStatuses.map((status) => ({
value: status,
@@ -28,6 +34,7 @@ const StatusFilterComponent: React.FC = ({ stats, selectedStatus, onStatu
{` (${stats[status]})`}