forked from elastic/kibana
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[SIEM][CASES] Configure cases: Final (elastic#59358)
* Create action schema * Create createRequestHandler util function * Add actions plugins * Create action * Validate actionTypeId * [SIEM][CASE] Add find actions schema * Create find actions route * Create HttpRequestError * Support http status codes * Create check action health types * Create check action health route * Show field mapping * Leave spaces between sections * Export CasesConfiguration from servicenow action type * Create IdSchema * Create UpdateCaseConfiguration interface * Create update action route * Add constants * Create fetchConnectors api function * Create useConnector * Create reducer * Dynamic connectors * Fix conflicts * Create servicenow connector * Register servicenow connector * Add ServiceNow logo * Create connnectors mapping * Create validators in utils * Use validators in connectors * Validate URL * Use connectors from config * Enable triggers_aciton_ui plugin * Show flyout * Add closures options * cleanup configure api * simplify UI + add configure API * Add mapping to flyout * Fix error * add all plumbing and main functionality to get configure working * Fix naming * Fix tests * Show error when failed * Remove version from query * Disable when loading connectors * fix config update * Fix flyout * fix two bugs * Change defaults * Disable closure options when no connector is selected * Use default mappings from lib * Set mapping if empty * Reset connector to none if deleted from settings * Change lib structure * fix type * review with christos * Do not patch connector with id none * Fix bug * Show icon in dropdown * Rename variable * Show callout when connectors does not exists * Adapt to new error handling * Fix rebase wrong resolve * Improve errors * Remove async * Fix spelling * Refactor hooks * Fix naming * Better translation * Fix bug with different action type attributes * Fix linting errors * Remove unnecessary comment * Fix translation * Normalized mapping before updating connector * Fix type * Memoized capitalized * Dynamic data-subj-test variable * Fix routes Co-authored-by: Xavier Mouligneau <189600+XavierM@users.noreply.github.com>
- Loading branch information
Showing
49 changed files
with
2,079 additions
and
166 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
x-pack/legacy/plugins/siem/public/containers/case/configure/api.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import { isEmpty } from 'lodash/fp'; | ||
import { | ||
CasesConnectorsFindResult, | ||
CasesConfigurePatch, | ||
CasesConfigureResponse, | ||
CasesConfigureRequest, | ||
} from '../../../../../../../plugins/case/common/api'; | ||
import { KibanaServices } from '../../../lib/kibana'; | ||
|
||
import { CASES_CONFIGURE_URL } from '../constants'; | ||
import { ApiProps } from '../types'; | ||
import { convertToCamelCase, decodeCaseConfigureResponse } from '../utils'; | ||
import { CaseConfigure, PatchConnectorProps } from './types'; | ||
|
||
export const fetchConnectors = async ({ signal }: ApiProps): Promise<CasesConnectorsFindResult> => { | ||
const response = await KibanaServices.get().http.fetch( | ||
`${CASES_CONFIGURE_URL}/connectors/_find`, | ||
{ | ||
method: 'GET', | ||
signal, | ||
} | ||
); | ||
|
||
return response; | ||
}; | ||
|
||
export const getCaseConfigure = async ({ signal }: ApiProps): Promise<CaseConfigure | null> => { | ||
const response = await KibanaServices.get().http.fetch<CasesConfigureResponse>( | ||
CASES_CONFIGURE_URL, | ||
{ | ||
method: 'GET', | ||
signal, | ||
} | ||
); | ||
|
||
return !isEmpty(response) | ||
? convertToCamelCase<CasesConfigureResponse, CaseConfigure>( | ||
decodeCaseConfigureResponse(response) | ||
) | ||
: null; | ||
}; | ||
|
||
export const postCaseConfigure = async ( | ||
caseConfiguration: CasesConfigureRequest, | ||
signal: AbortSignal | ||
): Promise<CaseConfigure> => { | ||
const response = await KibanaServices.get().http.fetch<CasesConfigureResponse>( | ||
CASES_CONFIGURE_URL, | ||
{ | ||
method: 'POST', | ||
body: JSON.stringify(caseConfiguration), | ||
signal, | ||
} | ||
); | ||
return convertToCamelCase<CasesConfigureResponse, CaseConfigure>( | ||
decodeCaseConfigureResponse(response) | ||
); | ||
}; | ||
|
||
export const patchCaseConfigure = async ( | ||
caseConfiguration: CasesConfigurePatch, | ||
signal: AbortSignal | ||
): Promise<CaseConfigure> => { | ||
const response = await KibanaServices.get().http.fetch<CasesConfigureResponse>( | ||
CASES_CONFIGURE_URL, | ||
{ | ||
method: 'PATCH', | ||
body: JSON.stringify(caseConfiguration), | ||
signal, | ||
} | ||
); | ||
return convertToCamelCase<CasesConfigureResponse, CaseConfigure>( | ||
decodeCaseConfigureResponse(response) | ||
); | ||
}; | ||
|
||
export const patchConfigConnector = async ({ | ||
connectorId, | ||
config, | ||
signal, | ||
}: PatchConnectorProps): Promise<CasesConnectorsFindResult> => { | ||
const response = await KibanaServices.get().http.fetch( | ||
`${CASES_CONFIGURE_URL}/connectors/${connectorId}`, | ||
{ | ||
method: 'PATCH', | ||
body: JSON.stringify(config), | ||
signal, | ||
} | ||
); | ||
|
||
return response; | ||
}; |
43 changes: 43 additions & 0 deletions
43
x-pack/legacy/plugins/siem/public/containers/case/configure/types.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import { ElasticUser, ApiProps } from '../types'; | ||
import { | ||
ActionType, | ||
CasesConnectorConfiguration, | ||
CasesConfigurationMaps, | ||
CaseField, | ||
ClosureType, | ||
Connector, | ||
ThirdPartyField, | ||
} from '../../../../../../../plugins/case/common/api'; | ||
|
||
export { ActionType, CasesConfigurationMaps, CaseField, ClosureType, Connector, ThirdPartyField }; | ||
|
||
export interface CasesConfigurationMapping { | ||
source: CaseField; | ||
target: ThirdPartyField; | ||
actionType: ActionType; | ||
} | ||
|
||
export interface CaseConfigure { | ||
createdAt: string; | ||
createdBy: ElasticUser; | ||
connectorId: string; | ||
closureType: ClosureType; | ||
updatedAt: string; | ||
updatedBy: ElasticUser; | ||
version: string; | ||
} | ||
|
||
export interface PatchConnectorProps extends ApiProps { | ||
connectorId: string; | ||
config: CasesConnectorConfiguration; | ||
} | ||
|
||
export interface CCMapsCombinedActionAttributes extends CasesConfigurationMaps { | ||
actionType?: ActionType; | ||
} |
129 changes: 129 additions & 0 deletions
129
x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import { useState, useEffect, useCallback } from 'react'; | ||
import { getCaseConfigure, patchCaseConfigure, postCaseConfigure } from './api'; | ||
|
||
import { useStateToaster, errorToToaster } from '../../../components/toasters'; | ||
import * as i18n from '../translations'; | ||
import { ClosureType } from './types'; | ||
|
||
interface PersistCaseConfigure { | ||
connectorId: string; | ||
closureType: ClosureType; | ||
} | ||
|
||
export interface ReturnUseCaseConfigure { | ||
loading: boolean; | ||
refetchCaseConfigure: () => void; | ||
persistCaseConfigure: ({ connectorId, closureType }: PersistCaseConfigure) => unknown; | ||
persistLoading: boolean; | ||
} | ||
|
||
interface UseCaseConfigure { | ||
setConnectorId: (newConnectorId: string) => void; | ||
setClosureType: (newClosureType: ClosureType) => void; | ||
} | ||
|
||
export const useCaseConfigure = ({ | ||
setConnectorId, | ||
setClosureType, | ||
}: UseCaseConfigure): ReturnUseCaseConfigure => { | ||
const [, dispatchToaster] = useStateToaster(); | ||
const [loading, setLoading] = useState(true); | ||
const [persistLoading, setPersistLoading] = useState(false); | ||
const [version, setVersion] = useState(''); | ||
|
||
const refetchCaseConfigure = useCallback(() => { | ||
let didCancel = false; | ||
const abortCtrl = new AbortController(); | ||
|
||
const fetchCaseConfiguration = async () => { | ||
try { | ||
setLoading(true); | ||
const res = await getCaseConfigure({ signal: abortCtrl.signal }); | ||
if (!didCancel) { | ||
setLoading(false); | ||
if (res != null) { | ||
setConnectorId(res.connectorId); | ||
setClosureType(res.closureType); | ||
setVersion(res.version); | ||
} | ||
} | ||
} catch (error) { | ||
if (!didCancel) { | ||
setLoading(false); | ||
errorToToaster({ | ||
title: i18n.ERROR_TITLE, | ||
error: error.body && error.body.message ? new Error(error.body.message) : error, | ||
dispatchToaster, | ||
}); | ||
} | ||
} | ||
}; | ||
|
||
fetchCaseConfiguration(); | ||
|
||
return () => { | ||
didCancel = true; | ||
abortCtrl.abort(); | ||
}; | ||
}, []); | ||
|
||
const persistCaseConfigure = useCallback( | ||
async ({ connectorId, closureType }: PersistCaseConfigure) => { | ||
let didCancel = false; | ||
const abortCtrl = new AbortController(); | ||
const saveCaseConfiguration = async () => { | ||
try { | ||
setPersistLoading(true); | ||
const res = | ||
version.length === 0 | ||
? await postCaseConfigure( | ||
{ connector_id: connectorId, closure_type: closureType }, | ||
abortCtrl.signal | ||
) | ||
: await patchCaseConfigure( | ||
{ connector_id: connectorId, closure_type: closureType, version }, | ||
abortCtrl.signal | ||
); | ||
if (!didCancel) { | ||
setPersistLoading(false); | ||
setConnectorId(res.connectorId); | ||
setClosureType(res.closureType); | ||
setVersion(res.version); | ||
} | ||
} catch (error) { | ||
if (!didCancel) { | ||
setPersistLoading(false); | ||
errorToToaster({ | ||
title: i18n.ERROR_TITLE, | ||
error: error.body && error.body.message ? new Error(error.body.message) : error, | ||
dispatchToaster, | ||
}); | ||
} | ||
} | ||
}; | ||
saveCaseConfiguration(); | ||
return () => { | ||
didCancel = true; | ||
abortCtrl.abort(); | ||
}; | ||
}, | ||
[version] | ||
); | ||
|
||
useEffect(() => { | ||
refetchCaseConfigure(); | ||
}, []); | ||
|
||
return { | ||
loading, | ||
refetchCaseConfigure, | ||
persistCaseConfigure, | ||
persistLoading, | ||
}; | ||
}; |
115 changes: 115 additions & 0 deletions
115
x-pack/legacy/plugins/siem/public/containers/case/configure/use_connectors.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import { useState, useEffect, useCallback } from 'react'; | ||
|
||
import { useStateToaster, errorToToaster } from '../../../components/toasters'; | ||
import * as i18n from '../translations'; | ||
import { fetchConnectors, patchConfigConnector } from './api'; | ||
import { CasesConfigurationMapping, Connector } from './types'; | ||
|
||
export interface ReturnConnectors { | ||
loading: boolean; | ||
connectors: Connector[]; | ||
refetchConnectors: () => void; | ||
updateConnector: (connectorId: string, mappings: CasesConfigurationMapping[]) => unknown; | ||
} | ||
|
||
export const useConnectors = (): ReturnConnectors => { | ||
const [, dispatchToaster] = useStateToaster(); | ||
const [loading, setLoading] = useState(true); | ||
const [connectors, setConnectors] = useState<Connector[]>([]); | ||
|
||
const refetchConnectors = useCallback(() => { | ||
let didCancel = false; | ||
const abortCtrl = new AbortController(); | ||
const getConnectors = async () => { | ||
try { | ||
setLoading(true); | ||
const res = await fetchConnectors({ signal: abortCtrl.signal }); | ||
if (!didCancel) { | ||
setLoading(false); | ||
setConnectors(res.data); | ||
} | ||
} catch (error) { | ||
if (!didCancel) { | ||
setLoading(false); | ||
setConnectors([]); | ||
errorToToaster({ | ||
title: i18n.ERROR_TITLE, | ||
error: error.body && error.body.message ? new Error(error.body.message) : error, | ||
dispatchToaster, | ||
}); | ||
} | ||
} | ||
}; | ||
getConnectors(); | ||
return () => { | ||
didCancel = true; | ||
abortCtrl.abort(); | ||
}; | ||
}, []); | ||
|
||
const updateConnector = useCallback( | ||
(connectorId: string, mappings: CasesConfigurationMapping[]) => { | ||
if (connectorId === 'none') { | ||
return; | ||
} | ||
|
||
let didCancel = false; | ||
const abortCtrl = new AbortController(); | ||
const update = async () => { | ||
try { | ||
setLoading(true); | ||
await patchConfigConnector({ | ||
connectorId, | ||
config: { | ||
cases_configuration: { | ||
mapping: mappings.map(m => ({ | ||
source: m.source, | ||
target: m.target, | ||
action_type: m.actionType, | ||
})), | ||
}, | ||
}, | ||
signal: abortCtrl.signal, | ||
}); | ||
if (!didCancel) { | ||
setLoading(false); | ||
refetchConnectors(); | ||
} | ||
} catch (error) { | ||
if (!didCancel) { | ||
setLoading(false); | ||
refetchConnectors(); | ||
errorToToaster({ | ||
title: i18n.ERROR_TITLE, | ||
error: error.body && error.body.message ? new Error(error.body.message) : error, | ||
dispatchToaster, | ||
}); | ||
} | ||
} | ||
}; | ||
update(); | ||
return () => { | ||
didCancel = true; | ||
abortCtrl.abort(); | ||
}; | ||
}, | ||
[] | ||
); | ||
|
||
useEffect(() => { | ||
refetchConnectors(); | ||
}, []); | ||
|
||
return { | ||
loading, | ||
connectors, | ||
refetchConnectors, | ||
updateConnector, | ||
}; | ||
}; |
Oops, something went wrong.