Skip to content

Commit 98aa1d2

Browse files
authored
[SIEM] [Case] Enable case by default. Snake to camel on UI (#57936)
1 parent 9c8c47b commit 98aa1d2

File tree

26 files changed

+605
-101
lines changed

26 files changed

+605
-101
lines changed

x-pack/legacy/plugins/siem/public/components/links/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ const CaseDetailsLinkComponent: React.FC<{ children?: React.ReactNode; detailNam
4444
children,
4545
detailName,
4646
}) => (
47-
<EuiLink href={getCaseDetailsUrl(encodeURIComponent(detailName))}>
47+
<EuiLink
48+
href={getCaseDetailsUrl(encodeURIComponent(detailName))}
49+
data-test-subj="case-details-link"
50+
>
4851
{children ? children : detailName}
4952
</EuiLink>
5053
);

x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ describe('SIEM Navigation', () => {
6767
detailName: undefined,
6868
navTabs: {
6969
case: {
70-
disabled: true,
70+
disabled: false,
7171
href: '#/link-to/case',
7272
id: 'case',
7373
name: 'Case',
@@ -160,7 +160,7 @@ describe('SIEM Navigation', () => {
160160
filters: [],
161161
navTabs: {
162162
case: {
163-
disabled: true,
163+
disabled: false,
164164
href: '#/link-to/case',
165165
id: 'case',
166166
name: 'Case',

x-pack/legacy/plugins/siem/public/containers/case/api.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
*/
66

77
import { KibanaServices } from '../../lib/kibana';
8-
import { AllCases, FetchCasesProps, Case, NewCase, SortFieldCase } from './types';
9-
import { Direction } from '../../graphql/types';
8+
import { FetchCasesProps, Case, NewCase, SortFieldCase, AllCases, CaseSnake } from './types';
109
import { throwIfNotOk } from '../../hooks/api/api';
1110
import { CASES_URL } from './constants';
11+
import { convertToCamelCase, convertAllCasesToCamel } from './utils';
1212

13-
export const getCase = async (caseId: string, includeComments: boolean) => {
13+
export const getCase = async (caseId: string, includeComments: boolean): Promise<Case> => {
1414
const response = await KibanaServices.get().http.fetch(`${CASES_URL}/${caseId}`, {
1515
method: 'GET',
1616
asResponse: true,
@@ -19,7 +19,7 @@ export const getCase = async (caseId: string, includeComments: boolean) => {
1919
},
2020
});
2121
await throwIfNotOk(response.response);
22-
return response.body!;
22+
return convertToCamelCase<CaseSnake, Case>(response.body!);
2323
};
2424

2525
export const getCases = async ({
@@ -31,7 +31,7 @@ export const getCases = async ({
3131
page: 1,
3232
perPage: 20,
3333
sortField: SortFieldCase.createdAt,
34-
sortOrder: Direction.desc,
34+
sortOrder: 'desc',
3535
},
3636
}: FetchCasesProps): Promise<AllCases> => {
3737
const tags = [...(filterOptions.tags?.map(t => `case-workflow.attributes.tags: ${t}`) ?? [])];
@@ -46,7 +46,7 @@ export const getCases = async ({
4646
asResponse: true,
4747
});
4848
await throwIfNotOk(response.response);
49-
return response.body!;
49+
return convertAllCasesToCamel(response.body!);
5050
};
5151

5252
export const createCase = async (newCase: NewCase): Promise<Case> => {
@@ -56,18 +56,19 @@ export const createCase = async (newCase: NewCase): Promise<Case> => {
5656
body: JSON.stringify(newCase),
5757
});
5858
await throwIfNotOk(response.response);
59-
return response.body!;
59+
return convertToCamelCase<CaseSnake, Case>(response.body!);
6060
};
6161

6262
export const updateCaseProperty = async (
6363
caseId: string,
64-
updatedCase: Partial<Case>
64+
updatedCase: Partial<Case>,
65+
version: string
6566
): Promise<Partial<Case>> => {
6667
const response = await KibanaServices.get().http.fetch(`${CASES_URL}/${caseId}`, {
6768
method: 'PATCH',
6869
asResponse: true,
69-
body: JSON.stringify(updatedCase),
70+
body: JSON.stringify({ case: updatedCase, version }),
7071
});
7172
await throwIfNotOk(response.response);
72-
return response.body!;
73+
return convertToCamelCase<Partial<CaseSnake>, Partial<Case>>(response.body!);
7374
};

x-pack/legacy/plugins/siem/public/containers/case/types.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
import { Direction } from '../../graphql/types';
87
interface FormData {
98
isNew?: boolean;
109
}
@@ -15,44 +14,69 @@ export interface NewCase extends FormData {
1514
title: string;
1615
}
1716

18-
export interface Case {
17+
export interface CaseSnake {
1918
case_id: string;
2019
created_at: string;
21-
created_by: ElasticUser;
20+
created_by: ElasticUserSnake;
2221
description: string;
2322
state: string;
2423
tags: string[];
2524
title: string;
2625
updated_at: string;
26+
version?: string;
27+
}
28+
29+
export interface Case {
30+
caseId: string;
31+
createdAt: string;
32+
createdBy: ElasticUser;
33+
description: string;
34+
state: string;
35+
tags: string[];
36+
title: string;
37+
updatedAt: string;
38+
version?: string;
2739
}
2840

2941
export interface QueryParams {
3042
page: number;
3143
perPage: number;
3244
sortField: SortFieldCase;
33-
sortOrder: Direction;
45+
sortOrder: 'asc' | 'desc';
3446
}
3547

3648
export interface FilterOptions {
3749
search: string;
3850
tags: string[];
3951
}
4052

53+
export interface AllCasesSnake {
54+
cases: CaseSnake[];
55+
page: number;
56+
per_page: number;
57+
total: number;
58+
}
59+
4160
export interface AllCases {
4261
cases: Case[];
4362
page: number;
44-
per_page: number;
63+
perPage: number;
4564
total: number;
4665
}
4766
export enum SortFieldCase {
48-
createdAt = 'created_at',
67+
createdAt = 'createdAt',
4968
state = 'state',
50-
updatedAt = 'updated_at',
69+
updatedAt = 'updatedAt',
70+
}
71+
72+
export interface ElasticUserSnake {
73+
readonly username: string;
74+
readonly full_name?: string | null;
5175
}
5276

5377
export interface ElasticUser {
5478
readonly username: string;
55-
readonly full_name?: string;
79+
readonly fullName?: string | null;
5680
}
5781

5882
export interface FetchCasesProps {

x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,16 @@ const dataFetchReducer = (state: CaseState, action: Action): CaseState => {
5050
}
5151
};
5252
const initialData: Case = {
53-
case_id: '',
54-
created_at: '',
55-
created_by: {
53+
caseId: '',
54+
createdAt: '',
55+
createdBy: {
5656
username: '',
5757
},
5858
description: '',
5959
state: '',
6060
tags: [],
6161
title: '',
62-
updated_at: '',
62+
updatedAt: '',
6363
};
6464

6565
export const useGetCase = (caseId: string): [CaseState] => {

x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import {
1717
} from './constants';
1818
import { AllCases, SortFieldCase, FilterOptions, QueryParams } from './types';
1919
import { getTypedPayload } from './utils';
20-
import { Direction } from '../../graphql/types';
2120
import { errorToToaster } from '../../components/ml/api/error_to_toaster';
2221
import { useStateToaster } from '../../components/toasters';
2322
import * as i18n from './translations';
@@ -31,16 +30,9 @@ export interface UseGetCasesState {
3130
filterOptions: FilterOptions;
3231
}
3332

34-
export interface QueryArgs {
35-
page?: number;
36-
perPage?: number;
37-
sortField?: SortFieldCase;
38-
sortOrder?: Direction;
39-
}
40-
4133
export interface Action {
4234
type: string;
43-
payload?: AllCases | QueryArgs | FilterOptions;
35+
payload?: AllCases | Partial<QueryParams> | FilterOptions;
4436
}
4537
const dataFetchReducer = (state: UseGetCasesState, action: Action): UseGetCasesState => {
4638
switch (action.type) {
@@ -83,13 +75,13 @@ const dataFetchReducer = (state: UseGetCasesState, action: Action): UseGetCasesS
8375

8476
const initialData: AllCases = {
8577
page: 0,
86-
per_page: 0,
78+
perPage: 0,
8779
total: 0,
8880
cases: [],
8981
};
9082
export const useGetCases = (): [
9183
UseGetCasesState,
92-
Dispatch<SetStateAction<QueryArgs>>,
84+
Dispatch<SetStateAction<Partial<QueryParams>>>,
9385
Dispatch<SetStateAction<FilterOptions>>
9486
] => {
9587
const [state, dispatch] = useReducer(dataFetchReducer, {
@@ -104,11 +96,11 @@ export const useGetCases = (): [
10496
page: DEFAULT_TABLE_ACTIVE_PAGE,
10597
perPage: DEFAULT_TABLE_LIMIT,
10698
sortField: SortFieldCase.createdAt,
107-
sortOrder: Direction.desc,
99+
sortOrder: 'desc',
108100
},
109101
});
110-
const [queryParams, setQueryParams] = useState(state.queryParams as QueryArgs);
111-
const [filterQuery, setFilters] = useState(state.filterOptions as FilterOptions);
102+
const [queryParams, setQueryParams] = useState<Partial<QueryParams>>(state.queryParams);
103+
const [filterQuery, setFilters] = useState<FilterOptions>(state.filterOptions);
112104
const [, dispatchToaster] = useStateToaster();
113105

114106
useEffect(() => {

x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,11 @@ export const useUpdateCase = (
9696
const updateData = async (updateKey: keyof Case) => {
9797
dispatch({ type: FETCH_INIT });
9898
try {
99-
const response = await updateCaseProperty(caseId, { [updateKey]: state.data[updateKey] });
99+
const response = await updateCaseProperty(
100+
caseId,
101+
{ [updateKey]: state.data[updateKey] },
102+
state.data.version ?? '' // saved object versions are typed as string | undefined, hope that's not true
103+
);
100104
dispatch({ type: FETCH_SUCCESS, payload: response });
101105
} catch (error) {
102106
errorToToaster({ title: i18n.ERROR_TITLE, error, dispatchToaster });

x-pack/legacy/plugins/siem/public/containers/case/utils.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,37 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7+
import { camelCase, isArray, isObject, set } from 'lodash';
8+
import { AllCases, AllCasesSnake, Case, CaseSnake } from './types';
9+
710
export const getTypedPayload = <T>(a: unknown): T => a as T;
11+
12+
export const convertArrayToCamelCase = (arrayOfSnakes: unknown[]): unknown[] =>
13+
arrayOfSnakes.reduce((acc: unknown[], value) => {
14+
if (isArray(value)) {
15+
return [...acc, convertArrayToCamelCase(value)];
16+
} else if (isObject(value)) {
17+
return [...acc, convertToCamelCase(value)];
18+
} else {
19+
return [...acc, value];
20+
}
21+
}, []);
22+
23+
export const convertToCamelCase = <T, U extends {}>(snakeCase: T): U =>
24+
Object.entries(snakeCase).reduce((acc, [key, value]) => {
25+
if (isArray(value)) {
26+
set(acc, camelCase(key), convertArrayToCamelCase(value));
27+
} else if (isObject(value)) {
28+
set(acc, camelCase(key), convertToCamelCase(value));
29+
} else {
30+
set(acc, camelCase(key), value);
31+
}
32+
return acc;
33+
}, {} as U);
34+
35+
export const convertAllCasesToCamel = (snakeCases: AllCasesSnake): AllCases => ({
36+
cases: snakeCases.cases.map(snakeCase => convertToCamelCase<CaseSnake, Case>(snakeCase)),
37+
page: snakeCases.page,
38+
perPage: snakeCases.per_page,
39+
total: snakeCases.total,
40+
});
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { SortFieldCase } from '../../../../../containers/case/types';
8+
import { UseGetCasesState } from '../../../../../containers/case/use_get_cases';
9+
10+
export const useGetCasesMockState: UseGetCasesState = {
11+
data: {
12+
cases: [
13+
{
14+
caseId: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15',
15+
createdAt: '2020-02-13T19:44:23.627Z',
16+
createdBy: { username: 'elastic' },
17+
description: 'Security banana Issue',
18+
state: 'open',
19+
tags: ['defacement'],
20+
title: 'Another horrible breach',
21+
updatedAt: '2020-02-13T19:44:23.627Z',
22+
},
23+
{
24+
caseId: '362a5c10-4e99-11ea-9290-35d05cb55c15',
25+
createdAt: '2020-02-13T19:44:13.328Z',
26+
createdBy: { username: 'elastic' },
27+
description: 'Security banana Issue',
28+
state: 'open',
29+
tags: ['phishing'],
30+
title: 'Bad email',
31+
updatedAt: '2020-02-13T19:44:13.328Z',
32+
},
33+
{
34+
caseId: '34f8b9e0-4e99-11ea-9290-35d05cb55c15',
35+
createdAt: '2020-02-13T19:44:11.328Z',
36+
createdBy: { username: 'elastic' },
37+
description: 'Security banana Issue',
38+
state: 'open',
39+
tags: ['phishing'],
40+
title: 'Bad email',
41+
updatedAt: '2020-02-13T19:44:11.328Z',
42+
},
43+
{
44+
caseId: '31890e90-4e99-11ea-9290-35d05cb55c15',
45+
createdAt: '2020-02-13T19:44:05.563Z',
46+
createdBy: { username: 'elastic' },
47+
description: 'Security banana Issue',
48+
state: 'closed',
49+
tags: ['phishing'],
50+
title: 'Uh oh',
51+
updatedAt: '2020-02-18T21:32:24.056Z',
52+
},
53+
{
54+
caseId: '2f5b3210-4e99-11ea-9290-35d05cb55c15',
55+
createdAt: '2020-02-13T19:44:01.901Z',
56+
createdBy: { username: 'elastic' },
57+
description: 'Security banana Issue',
58+
state: 'open',
59+
tags: ['phishing'],
60+
title: 'Uh oh',
61+
updatedAt: '2020-02-13T19:44:01.901Z',
62+
},
63+
],
64+
page: 1,
65+
perPage: 5,
66+
total: 10,
67+
},
68+
isLoading: false,
69+
isError: false,
70+
queryParams: {
71+
page: 1,
72+
perPage: 5,
73+
sortField: SortFieldCase.createdAt,
74+
sortOrder: 'desc',
75+
},
76+
filterOptions: { search: '', tags: [] },
77+
};

0 commit comments

Comments
 (0)