From b9dd690a2609f0cb674deeac9d77957670cd97d7 Mon Sep 17 00:00:00 2001 From: stephmilovic Date: Sat, 7 Mar 2020 11:26:13 -0600 Subject: [PATCH 01/13] shouldve committed earlier damnit --- .../siem/public/containers/case/api.ts | 10 ++ .../siem/public/containers/case/constants.ts | 2 - .../containers/case/use_delete_cases.tsx | 119 ++++++++++++++++++ .../public/containers/case/use_get_cases.tsx | 2 + .../pages/case/components/all_cases/index.tsx | 59 +++++++-- .../pages/case/components/case_view/index.tsx | 46 +++++-- .../components/confirm_delete_case/index.tsx | 44 +++++++ .../confirm_delete_case/translations.ts | 22 ++++ .../siem/public/pages/case/translations.ts | 20 +-- 9 files changed, 290 insertions(+), 34 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/translations.ts diff --git a/x-pack/legacy/plugins/siem/public/containers/case/api.ts b/x-pack/legacy/plugins/siem/public/containers/case/api.ts index 81f8f83217e11..f3c86b36f8869 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/api.ts @@ -130,3 +130,13 @@ export const patchComment = async ( await throwIfNotOk(response.response); return convertToCamelCase(decodeCommentResponse(response.body)); }; + +export const deleteCases = async (caseIds: string[]): Promise => { + const response = await KibanaServices.get().http.fetch(`${CASES_URL}`, { + method: 'DELETE', + asResponse: true, + query: { ids: JSON.stringify(caseIds) }, + }); + await throwIfNotOk(response.response); + return response.body === 'true' ? true : false; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/constants.ts b/x-pack/legacy/plugins/siem/public/containers/case/constants.ts index ac62ba7b6f997..2dabbdbc2399a 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/constants.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/constants.ts @@ -10,8 +10,6 @@ export const DEFAULT_TABLE_LIMIT = 5; export const FETCH_FAILURE = 'FETCH_FAILURE'; export const FETCH_INIT = 'FETCH_INIT'; export const FETCH_SUCCESS = 'FETCH_SUCCESS'; -export const POST_NEW_CASE = 'POST_NEW_CASE'; -export const POST_NEW_COMMENT = 'POST_NEW_COMMENT'; export const UPDATE_FILTER_OPTIONS = 'UPDATE_FILTER_OPTIONS'; export const UPDATE_TABLE_SELECTIONS = 'UPDATE_TABLE_SELECTIONS'; export const UPDATE_QUERY_PARAMS = 'UPDATE_QUERY_PARAMS'; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx new file mode 100644 index 0000000000000..bdcf129d0941e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx @@ -0,0 +1,119 @@ +/* + * 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 { useCallback, useReducer } from 'react'; +import { errorToToaster } from '../../components/ml/api/error_to_toaster'; +import * as i18n from './translations'; +import { useStateToaster } from '../../components/toasters'; +import { deleteCases } from './api'; + +interface DeleteState { + isDisplayConfirmDeleteModal: boolean; + isDeleted: boolean; + isLoading: boolean; + isError: boolean; +} +type Action = + | { type: 'DISPLAY_MODAL'; payload: boolean } + | { type: 'FETCH_INIT' } + | { type: 'FETCH_SUCCESS'; payload: boolean } + | { type: 'FETCH_FAILURE' } + | { type: 'RESET_IS_DELETED'; payload: boolean }; + +const dataFetchReducer = (state: DeleteState, action: Action): DeleteState => { + switch (action.type) { + case 'DISPLAY_MODAL': + return { + ...state, + isDisplayConfirmDeleteModal: action.payload, + }; + case 'FETCH_INIT': + return { + ...state, + isLoading: true, + isError: false, + }; + case 'FETCH_SUCCESS': + return { + ...state, + isLoading: false, + isError: false, + isDeleted: action.payload, + }; + case 'FETCH_FAILURE': + return { + ...state, + isLoading: false, + isError: true, + }; + case 'RESET_IS_DELETED': + return { + ...state, + isDeleted: false, + }; + default: + throw new Error(); + } +}; +interface UseDeleteCase extends DeleteState { + dispatchResetIsDeleted: () => void; + handleOnDeleteConfirm: (caseIds: string[]) => void; + handleToggleModal: () => void; +} + +export const useDeleteCases = (): UseDeleteCase => { + const [state, dispatch] = useReducer(dataFetchReducer, { + isDisplayConfirmDeleteModal: false, + isLoading: true, + isError: false, + isDeleted: false, + }); + const [, dispatchToaster] = useStateToaster(); + + const dispatchDeleteCases = useCallback(async (caseIds: string[]) => { + let cancel = false; + try { + dispatch({ type: 'FETCH_INIT' }); + await deleteCases(caseIds); + if (!cancel) { + dispatch({ type: 'FETCH_SUCCESS', payload: true }); + } + } catch (error) { + if (!cancel) { + errorToToaster({ + title: i18n.ERROR_TITLE, + error: error.body && error.body.message ? new Error(error.body.message) : error, + dispatchToaster, + }); + dispatch({ type: 'FETCH_FAILURE' }); + } + } + return () => { + cancel = true; + }; + }, []); + + const dispatchToggleDeleteModal = useCallback(() => { + dispatch({ type: 'DISPLAY_MODAL', payload: !state.isDisplayConfirmDeleteModal }); + }, [state.isDisplayConfirmDeleteModal]); + + const dispatchResetIsDeleted = useCallback(() => { + dispatch({ type: 'RESET_IS_DELETED', payload: false }); + }, [state.isDisplayConfirmDeleteModal]); + + const handleOnDeleteConfirm = useCallback( + caseIds => { + dispatchDeleteCases(caseIds); + dispatchToggleDeleteModal(); + }, + [state.isDisplayConfirmDeleteModal] + ); + const handleToggleModal = useCallback(() => { + dispatchToggleDeleteModal(); + }, [state.isDisplayConfirmDeleteModal]); + + return { ...state, dispatchResetIsDeleted, handleOnDeleteConfirm, handleToggleModal }; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx index 76e9b5c138269..bd130a4092599 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx @@ -110,6 +110,7 @@ const initialData: AllCases = { interface UseGetCases extends UseGetCasesState { dispatchUpdateCaseProperty: ({ updateKey, updateValue, caseId, version }: UpdateCase) => void; getCaseCount: (caseState: keyof CaseCount) => void; + refetchCases: (filters: FilterOptions, queryParams: QueryParams) => void; setFilters: (filters: FilterOptions) => void; setQueryParams: (queryParams: QueryParams) => void; setSelectedCases: (mySelectedCases: Case[]) => void; @@ -250,6 +251,7 @@ export const useGetCases = (): UseGetCases => { ...state, dispatchUpdateCaseProperty, getCaseCount, + refetchCases: fetchCases, setFilters, setQueryParams, setSelectedCases, diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx index 484d9051ee43f..524310feb4591 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { EuiBasicTable, EuiButton, @@ -25,6 +25,7 @@ import { getCasesColumns } from './columns'; import { Case, FilterOptions, SortFieldCase } from '../../../../containers/case/types'; import { useGetCases } from '../../../../containers/case/use_get_cases'; +import { useDeleteCases } from '../../../../containers/case/use_delete_cases'; import { EuiBasicTableOnChange } from '../../../detection_engine/rules/types'; import { Panel } from '../../../../components/panel'; import { CasesTableFilters } from './table_filters'; @@ -41,6 +42,7 @@ import { getBulkItems } from '../bulk_actions'; import { CaseHeaderPage } from '../case_header_page'; import { OpenClosedStats } from '../open_closed_stats'; import { getActions } from './actions'; +import { ConfirmDeleteCaseModal } from '../confirm_delete_case'; const Div = styled.div` margin-top: ${({ theme }) => theme.eui.paddingSizes.m}; @@ -83,6 +85,7 @@ export const AllCases = React.memo(() => { loading, queryParams, selectedCases, + refetchCases, setFilters, setQueryParams, setSelectedCases, @@ -117,12 +120,6 @@ export const AllCases = React.memo(() => { [filterOptions, setFilters] ); - const actions = useMemo( - () => - getActions({ caseStatus: filterOptions.state, dispatchUpdate: dispatchUpdateCaseProperty }), - [filterOptions.state, dispatchUpdateCaseProperty] - ); - const memoizedGetCasesColumns = useMemo(() => getCasesColumns(actions), [filterOptions.state]); const memoizedPagination = useMemo( () => ({ @@ -163,6 +160,54 @@ export const AllCases = React.memo(() => { ); const isDataEmpty = useMemo(() => data.total === 0, [data]); + // Delete case + // const { + // dispatchResetIsDeleted, + // handleOnDeleteConfirm, + // handleToggleModal, + // isDeleted, + // isDisplayConfirmDeleteModal, + // } = useDeleteCases(); + // + // useEffect(() => { + // if (isDeleted) { + // refetchCases(filterOptions, queryParams); + // dispatchResetIsDeleted(); + // } + // }, [isDeleted, filterOptions, queryParams]); + // + // const [deleteThisCase, setDeleteThisCase] = useState({ + // title: '', + // id: '', + // }); + // const confirmDeleteModal = useMemo( + // () => ( + // + // ), + // [deleteThisCase, isDisplayConfirmDeleteModal] + // ); + // + // const toggleDeleteModal = useCallback( + // (deleteCase: Case) => { + // handleToggleModal(); + // setDeleteThisCase(deleteCase); + // }, + // [isDisplayConfirmDeleteModal] + // ); + + const actions = useMemo( + () => + getActions({ + caseStatus: filterOptions.state, + dispatchUpdate: dispatchUpdateCaseProperty, + }), + [filterOptions.state, dispatchUpdateCaseProperty] + ); return ( <> diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx index c917d27aebea3..5dc7407690986 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { EuiBadge, EuiButtonToggle, @@ -17,6 +17,7 @@ import { } from '@elastic/eui'; import styled, { css } from 'styled-components'; +import { Redirect } from 'react-router-dom'; import * as i18n from './translations'; import { Case } from '../../../../containers/case/types'; import { FormattedRelativePreferenceDate } from '../../../../components/formatted_date'; @@ -32,6 +33,9 @@ import { useUpdateCase } from '../../../../containers/case/use_update_case'; import { WrapperPage } from '../../../../components/wrapper_page'; import { getTypedPayload } from '../../../../containers/case/utils'; import { WhitePageWrapper } from '../wrappers'; +import { useDeleteCases } from '../../../../containers/case/use_delete_cases'; +import { SiemPageName } from '../../../home/types'; +import { ConfirmDeleteCaseModal } from '../confirm_delete_case'; interface Props { caseId: string; @@ -62,6 +66,7 @@ export interface CaseProps { export const CaseComponent = React.memo(({ caseId, initialData }) => { const { caseData, isLoading, updateKey, updateCaseProperty } = useUpdateCase(caseId, initialData); + // Update Fields const onUpdateField = useCallback( (newUpdateKey: keyof Case, updateValue: Case[keyof Case]) => { switch (newUpdateKey) { @@ -104,13 +109,38 @@ export const CaseComponent = React.memo(({ caseId, initialData }) => }, [updateCaseProperty, caseData.state] ); + const toggleStateCase = useCallback( + e => onUpdateField('state', e.target.checked ? 'open' : 'closed'), + [onUpdateField] + ); + const onSubmitTitle = useCallback(newTitle => onUpdateField('title', newTitle), [onUpdateField]); + const onSubmitTags = useCallback(newTags => onUpdateField('tags', newTags), [onUpdateField]); + + // Delete case + const { + handleToggleModal, + handleOnDeleteConfirm, + isDeleted, + isDisplayConfirmDeleteModal, + } = useDeleteCases(); + const confirmDeleteModal = useMemo( + () => ( + + ), + [isDisplayConfirmDeleteModal] + ); // TO DO refactor each of these const's into their own components const propertyActions = [ { iconType: 'trash', label: 'Delete case', - onClick: () => null, + onClick: handleToggleModal, }, { iconType: 'popout', @@ -124,12 +154,9 @@ export const CaseComponent = React.memo(({ caseId, initialData }) => }, ]; - const onSubmit = useCallback(newTitle => onUpdateField('title', newTitle), [onUpdateField]); - const toggleStateCase = useCallback( - e => onUpdateField('state', e.target.checked ? 'open' : 'closed'), - [onUpdateField] - ); - const onSubmitTags = useCallback(newTags => onUpdateField('tags', newTags), [onUpdateField]); + if (isDeleted) { + return ; + } return ( <> @@ -144,7 +171,7 @@ export const CaseComponent = React.memo(({ caseId, initialData }) => } title={caseData.title} @@ -222,6 +249,7 @@ export const CaseComponent = React.memo(({ caseId, initialData }) => + {confirmDeleteModal} ); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/index.tsx new file mode 100644 index 0000000000000..a984895458f0f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/index.tsx @@ -0,0 +1,44 @@ +/* + * 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 React from 'react'; +import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import * as i18n from './translations'; + +interface ConfirmDeleteCaseModalProps { + caseTitle: string; + isModalVisible: boolean; + onCancel: () => void; + onConfirm: () => void; +} + +const ConfirmDeleteCaseModalComp: React.FC = ({ + caseTitle, + isModalVisible, + onCancel, + onConfirm, +}) => { + if (!isModalVisible) { + return null; + } + return ( + + + {i18n.CONFIRM_QUESTION} + + + ); +}; + +export const ConfirmDeleteCaseModal = React.memo(ConfirmDeleteCaseModalComp); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/translations.ts new file mode 100644 index 0000000000000..c933e2caf63a2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/translations.ts @@ -0,0 +1,22 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +export * from '../../translations'; + +export const DELETE_TITLE = (caseTitle: string) => + i18n.translate('xpack.siem.case.confirmDeleteCase.deleteTitle', { + values: { caseTitle }, + defaultMessage: 'Delete "{caseTitle}"', + }); + +export const CONFIRM_QUESTION = i18n.translate( + 'xpack.siem.case.confirmDeleteCase.confirmQuestion', + { + defaultMessage: + 'By deleting this case, all related case data will be permanently removed and you will no longer be able to push data to a third-party case management system. Are you sure you wish to proceed?', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/translations.ts index fc64bd64ec4a2..0852622121d26 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/case/translations.ts @@ -14,6 +14,10 @@ export const CANCEL = i18n.translate('xpack.siem.case.caseView.cancel', { defaultMessage: 'Cancel', }); +export const DELETE_CASE = i18n.translate('xpack.siem.case.confirmDeleteCase.deleteCase', { + defaultMessage: 'Delete case', +}); + export const NAME = i18n.translate('xpack.siem.case.caseView.name', { defaultMessage: 'Name', }); @@ -60,26 +64,10 @@ export const OPTIONAL = i18n.translate('xpack.siem.case.caseView.optional', { defaultMessage: 'Optional', }); -export const LAST_UPDATED = i18n.translate('xpack.siem.case.caseView.updatedAt', { - defaultMessage: 'Last updated', -}); - -export const PAGE_SUBTITLE = i18n.translate('xpack.siem.case.caseView.pageSubtitle', { - defaultMessage: 'Cases within the Elastic SIEM', -}); - export const PAGE_TITLE = i18n.translate('xpack.siem.case.pageTitle', { defaultMessage: 'Cases', }); -export const STATE = i18n.translate('xpack.siem.case.caseView.state', { - defaultMessage: 'State', -}); - -export const SUBMIT = i18n.translate('xpack.siem.case.caseView.submit', { - defaultMessage: 'Submit', -}); - export const CREATE_CASE = i18n.translate('xpack.siem.case.caseView.createCase', { defaultMessage: 'Create case', }); From 354d94b0a36d19c4ba74c12f313d06c61ed18c2b Mon Sep 17 00:00:00 2001 From: stephmilovic Date: Sat, 7 Mar 2020 11:34:46 -0600 Subject: [PATCH 02/13] individual deleting complete --- .../public/containers/case/use_get_cases.tsx | 8 +- .../case/components/all_cases/actions.tsx | 5 +- .../pages/case/components/all_cases/index.tsx | 102 +++++++++--------- 3 files changed, 62 insertions(+), 53 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx index bd130a4092599..55ba7ff2affb9 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx @@ -247,11 +247,17 @@ export const useGetCases = (): UseGetCases => { [state.filterOptions, state.queryParams] ); + const refetchCases = useCallback(() => { + fetchCases(state.filterOptions, state.queryParams); + getCaseCount('open'); + getCaseCount('closed'); + }, [state.filterOptions, state.queryParams]); + return { ...state, dispatchUpdateCaseProperty, getCaseCount, - refetchCases: fetchCases, + refetchCases, setFilters, setQueryParams, setSelectedCases, diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/actions.tsx index 0ec09f2b57918..1042ff6970c74 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/actions.tsx @@ -13,18 +13,19 @@ import { UpdateCase } from '../../../../containers/case/use_get_cases'; interface GetActions { caseStatus: string; dispatchUpdate: Dispatch; + deleteCaseOnClick: (deleteCase: Case) => void; } export const getActions = ({ caseStatus, dispatchUpdate, + deleteCaseOnClick, }: GetActions): Array> => [ { description: i18n.DELETE, icon: 'trash', name: i18n.DELETE, - // eslint-disable-next-line no-console - onClick: ({ id }: Case) => console.log('TO DO Delete case', id), + onClick: (theCase: Case) => deleteCaseOnClick(theCase), type: 'icon', 'data-test-subj': 'action-delete', }, diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx index 524310feb4591..26cb341026b42 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx @@ -91,6 +91,56 @@ export const AllCases = React.memo(() => { setSelectedCases, } = useGetCases(); + // Delete case + const { + dispatchResetIsDeleted, + handleOnDeleteConfirm, + handleToggleModal, + isDeleted, + isDisplayConfirmDeleteModal, + } = useDeleteCases(); + + useEffect(() => { + if (isDeleted) { + refetchCases(filterOptions, queryParams); + dispatchResetIsDeleted(); + } + }, [isDeleted, filterOptions, queryParams]); + + const [deleteThisCase, setDeleteThisCase] = useState({ + title: '', + id: '', + }); + const confirmDeleteModal = useMemo( + () => ( + + ), + [deleteThisCase, isDisplayConfirmDeleteModal] + ); + + const toggleDeleteModal = useCallback( + (deleteCase: Case) => { + handleToggleModal(); + setDeleteThisCase(deleteCase); + }, + [isDisplayConfirmDeleteModal] + ); + + const actions = useMemo( + () => + getActions({ + caseStatus: filterOptions.state, + deleteCaseOnClick: toggleDeleteModal, + dispatchUpdate: dispatchUpdateCaseProperty, + }), + [filterOptions.state, dispatchUpdateCaseProperty] + ); + const tableOnChangeCallback = useCallback( ({ page, sort }: EuiBasicTableOnChange) => { let newQueryParams = queryParams; @@ -159,55 +209,6 @@ export const AllCases = React.memo(() => { [loading] ); const isDataEmpty = useMemo(() => data.total === 0, [data]); - - // Delete case - // const { - // dispatchResetIsDeleted, - // handleOnDeleteConfirm, - // handleToggleModal, - // isDeleted, - // isDisplayConfirmDeleteModal, - // } = useDeleteCases(); - // - // useEffect(() => { - // if (isDeleted) { - // refetchCases(filterOptions, queryParams); - // dispatchResetIsDeleted(); - // } - // }, [isDeleted, filterOptions, queryParams]); - // - // const [deleteThisCase, setDeleteThisCase] = useState({ - // title: '', - // id: '', - // }); - // const confirmDeleteModal = useMemo( - // () => ( - // - // ), - // [deleteThisCase, isDisplayConfirmDeleteModal] - // ); - // - // const toggleDeleteModal = useCallback( - // (deleteCase: Case) => { - // handleToggleModal(); - // setDeleteThisCase(deleteCase); - // }, - // [isDisplayConfirmDeleteModal] - // ); - - const actions = useMemo( - () => - getActions({ - caseStatus: filterOptions.state, - dispatchUpdate: dispatchUpdateCaseProperty, - }), - [filterOptions.state, dispatchUpdateCaseProperty] - ); return ( <> @@ -282,7 +283,7 @@ export const AllCases = React.memo(() => { { )} + {confirmDeleteModal} ); }); From 27a08cc943777e7ff9350b191af740e0273ee1a8 Mon Sep 17 00:00:00 2001 From: stephmilovic Date: Sat, 7 Mar 2020 11:49:52 -0600 Subject: [PATCH 03/13] comment count implemented --- .../plugins/siem/public/containers/case/types.ts | 1 + .../siem/public/containers/case/use_get_case.tsx | 1 + .../pages/case/components/all_cases/columns.tsx | 12 +++++++----- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/containers/case/types.ts b/x-pack/legacy/plugins/siem/public/containers/case/types.ts index d479abdbd4489..c89993ec67179 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/types.ts @@ -16,6 +16,7 @@ export interface Comment { export interface Case { id: string; comments: Comment[]; + commentIds: string[]; createdAt: string; createdBy: ElasticUser; description: string; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx index 5f1dc96735d32..3106b71542067 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx @@ -53,6 +53,7 @@ const initialData: Case = { id: '', createdAt: '', comments: [], + commentIds: [], createdBy: { username: '', }, diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx index f6ed2694fdc40..db3313d843547 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx @@ -33,9 +33,8 @@ const Spacer = styled.span` margin-left: ${({ theme }) => theme.eui.paddingSizes.s}; `; -const TempNumberComponent = () => {1}; -TempNumberComponent.displayName = 'TempNumberComponent'; - +const renderStringField = (field: string, dataTestSubj: string) => + field != null ? {field} : getEmptyTagValue(); export const getCasesColumns = ( actions: Array> ): CasesColumns[] => [ @@ -59,6 +58,7 @@ export const getCasesColumns = ( } return getEmptyTagValue(); }, + width: '25%', }, { field: 'createdBy', @@ -101,13 +101,15 @@ export const getCasesColumns = ( return getEmptyTagValue(); }, truncateText: true, + width: '20%', }, { align: 'right', - field: 'commentCount', // TO DO once we have commentCount returned in the API: https://github.com/elastic/kibana/issues/58525 + field: 'commentIds', name: i18n.COMMENTS, sortable: true, - render: TempNumberComponent, + render: (comments: Case['commentIds']) => + renderStringField(`${comments.length}`, `case-table-column-commentCount`), }, { field: 'createdAt', From 20cfc9831fcbfb16d76569427de0befe27347a2d Mon Sep 17 00:00:00 2001 From: stephmilovic Date: Sat, 7 Mar 2020 12:29:53 -0600 Subject: [PATCH 04/13] add comment works --- .../components/markdown_editor/form.tsx | 3 +- .../components/markdown_editor/index.tsx | 3 ++ .../containers/case/use_post_comment.tsx | 32 ++++++++----- .../containers/case/use_update_comment.tsx | 45 +++++++++++-------- .../case/components/add_comment/index.tsx | 20 +++++++-- .../components/user_action_tree/index.tsx | 9 +++- 6 files changed, 73 insertions(+), 39 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/markdown_editor/form.tsx b/x-pack/legacy/plugins/siem/public/components/markdown_editor/form.tsx index 3c5287a6fac24..8ba0476fb0e05 100644 --- a/x-pack/legacy/plugins/siem/public/components/markdown_editor/form.tsx +++ b/x-pack/legacy/plugins/siem/public/components/markdown_editor/form.tsx @@ -5,7 +5,7 @@ */ import { EuiFormRow } from '@elastic/eui'; -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { FieldHook, getFieldValidityAndErrorMessage } from '../../shared_imports'; import { MarkdownEditor } from '.'; @@ -34,7 +34,6 @@ export const MarkdownEditorForm = ({ }, [field] ); - return ( { onChange(content); }, [content]); + useEffect(() => { + setContent(initialContent); + }, [initialContent]); const tabs = useMemo( () => [ { diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.tsx index 63d24e2935c2a..6bd78e60b72c4 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.tsx @@ -11,7 +11,6 @@ import { useStateToaster } from '../../components/toasters'; import { errorToToaster } from '../../components/ml/api/error_to_toaster'; import { postComment } from './api'; -import { FETCH_FAILURE, FETCH_INIT, FETCH_SUCCESS } from './constants'; import * as i18n from './translations'; import { Comment } from './types'; @@ -21,27 +20,33 @@ interface NewCommentState { isError: boolean; caseId: string; } -interface Action { - type: string; - payload?: Comment; -} +type Action = + | { type: 'RESET_COMMENT_DATA' } + | { type: 'FETCH_INIT' } + | { type: 'FETCH_SUCCESS'; payload: Comment } + | { type: 'FETCH_FAILURE' }; const dataFetchReducer = (state: NewCommentState, action: Action): NewCommentState => { switch (action.type) { - case FETCH_INIT: + case 'RESET_COMMENT_DATA': + return { + ...state, + commentData: null, + }; + case 'FETCH_INIT': return { ...state, isLoading: true, isError: false, }; - case FETCH_SUCCESS: + case 'FETCH_SUCCESS': return { ...state, isLoading: false, isError: false, commentData: action.payload ?? null, }; - case FETCH_FAILURE: + case 'FETCH_FAILURE': return { ...state, isLoading: false, @@ -54,6 +59,7 @@ const dataFetchReducer = (state: NewCommentState, action: Action): NewCommentSta interface UsePostComment extends NewCommentState { postComment: (data: CommentRequest) => void; + resetCommentData: () => void; } export const usePostComment = (caseId: string): UsePostComment => { @@ -68,10 +74,10 @@ export const usePostComment = (caseId: string): UsePostComment => { const postMyComment = useCallback(async (data: CommentRequest) => { let cancel = false; try { - dispatch({ type: FETCH_INIT }); + dispatch({ type: 'FETCH_INIT' }); const response = await postComment(data, state.caseId); if (!cancel) { - dispatch({ type: FETCH_SUCCESS, payload: response }); + dispatch({ type: 'FETCH_SUCCESS', payload: response }); } } catch (error) { if (!cancel) { @@ -80,7 +86,7 @@ export const usePostComment = (caseId: string): UsePostComment => { error: error.body && error.body.message ? new Error(error.body.message) : error, dispatchToaster, }); - dispatch({ type: FETCH_FAILURE }); + dispatch({ type: 'FETCH_FAILURE' }); } } return () => { @@ -88,5 +94,7 @@ export const usePostComment = (caseId: string): UsePostComment => { }; }, []); - return { ...state, postComment: postMyComment }; + const resetCommentData = useCallback(() => dispatch({ type: 'RESET_COMMENT_DATA' }), []); + + return { ...state, postComment: postMyComment, resetCommentData }; }; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx index d7649cb7d8fdb..42cbb616ff355 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx @@ -4,16 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useReducer, useCallback } from 'react'; +import { useReducer, useCallback, Dispatch } from 'react'; import { useStateToaster } from '../../components/toasters'; import { errorToToaster } from '../../components/ml/api/error_to_toaster'; import { patchComment } from './api'; -import { FETCH_FAILURE, FETCH_INIT, FETCH_SUCCESS } from './constants'; import * as i18n from './translations'; import { Comment } from './types'; -import { getTypedPayload } from './utils'; interface CommentUpdateState { comments: Comment[]; @@ -26,22 +24,28 @@ interface CommentUpdate { commentId: string; } -interface Action { - type: string; - payload?: CommentUpdate | string; -} +type Action = + | { type: 'APPEND_COMMENT'; payload: Comment } + | { type: 'FETCH_INIT'; payload: string } + | { type: 'FETCH_SUCCESS'; payload: CommentUpdate } + | { type: 'FETCH_FAILURE'; payload: string }; const dataFetchReducer = (state: CommentUpdateState, action: Action): CommentUpdateState => { switch (action.type) { - case FETCH_INIT: + case 'APPEND_COMMENT': return { ...state, - isLoadingIds: [...state.isLoadingIds, getTypedPayload(action.payload)], + comments: [...state.comments, action.payload], + }; + case 'FETCH_INIT': + return { + ...state, + isLoadingIds: [...state.isLoadingIds, action.payload], isError: false, }; - case FETCH_SUCCESS: - const updatePayload = getTypedPayload(action.payload); + case 'FETCH_SUCCESS': + const updatePayload = action.payload; const foundIndex = state.comments.findIndex( comment => comment.id === updatePayload.commentId ); @@ -56,12 +60,10 @@ const dataFetchReducer = (state: CommentUpdateState, action: Action): CommentUpd isError: false, comments: newComments, }; - case FETCH_FAILURE: + case 'FETCH_FAILURE': return { ...state, - isLoadingIds: state.isLoadingIds.filter( - id => getTypedPayload(action.payload) !== id - ), + isLoadingIds: state.isLoadingIds.filter(id => action.payload !== id), isError: true, }; default: @@ -71,6 +73,7 @@ const dataFetchReducer = (state: CommentUpdateState, action: Action): CommentUpd interface UseUpdateComment extends CommentUpdateState { updateComment: (commentId: string, commentUpdate: string) => void; + addPostedComment: Dispatch; } export const useUpdateComment = (comments: Comment[]): UseUpdateComment => { @@ -85,13 +88,13 @@ export const useUpdateComment = (comments: Comment[]): UseUpdateComment => { async (commentId: string, commentUpdate: string) => { let cancel = false; try { - dispatch({ type: FETCH_INIT, payload: commentId }); + dispatch({ type: 'FETCH_INIT', payload: commentId }); const currentComment = state.comments.find(comment => comment.id === commentId) ?? { version: '', }; const response = await patchComment(commentId, commentUpdate, currentComment.version); if (!cancel) { - dispatch({ type: FETCH_SUCCESS, payload: { update: response, commentId } }); + dispatch({ type: 'FETCH_SUCCESS', payload: { update: response, commentId } }); } } catch (error) { if (!cancel) { @@ -100,7 +103,7 @@ export const useUpdateComment = (comments: Comment[]): UseUpdateComment => { error: error.body && error.body.message ? new Error(error.body.message) : error, dispatchToaster, }); - dispatch({ type: FETCH_FAILURE, payload: commentId }); + dispatch({ type: 'FETCH_FAILURE', payload: commentId }); } } return () => { @@ -109,6 +112,10 @@ export const useUpdateComment = (comments: Comment[]): UseUpdateComment => { }, [state] ); + const addPostedComment = useCallback( + (comment: Comment) => dispatch({ type: 'APPEND_COMMENT', payload: comment }), + [] + ); - return { ...state, updateComment: dispatchUpdateComment }; + return { ...state, updateComment: dispatchUpdateComment, addPostedComment }; }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx index 16c6101b80d40..5c04daef74d3f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx @@ -5,7 +5,7 @@ */ import { EuiButton, EuiLoadingSpinner } from '@elastic/eui'; -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect } from 'react'; import styled from 'styled-components'; import { CommentRequest } from '../../../../../../../../plugins/case/common/api'; @@ -14,6 +14,7 @@ import { MarkdownEditorForm } from '../../../../components/markdown_editor/form' import { Form, useForm, UseField } from '../../../../shared_imports'; import * as i18n from '../../translations'; import { schema } from './schema'; +import { Comment } from '../../../../containers/case/types'; const MySpinner = styled(EuiLoadingSpinner)` position: absolute; @@ -25,16 +26,27 @@ const initialCommentValue: CommentRequest = { comment: '', }; -export const AddComment = React.memo<{ +interface AddCommentProps { caseId: string; -}>(({ caseId }) => { - const { commentData, isLoading, postComment } = usePostComment(caseId); + onCommentPosted: (commentResponse: Comment) => void; +} + +export const AddComment = React.memo(({ caseId, onCommentPosted }) => { + const { commentData, isLoading, postComment, resetCommentData } = usePostComment(caseId); const { form } = useForm({ defaultValue: initialCommentValue, options: { stripEmptyFields: false }, schema, }); + useEffect(() => { + if (commentData !== null) { + onCommentPosted(commentData); + form.reset(); + resetCommentData(); + } + }, [commentData]); + const onSubmit = useCallback(async () => { const { isValid, data } = await form.submit(); if (isValid) { diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx index b68bfd73e50e9..3e6c339d90456 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx @@ -24,7 +24,9 @@ const NewId = 'newComent'; export const UserActionTree = React.memo( ({ data: caseData, onUpdateField, isLoadingDescription }: UserActionTreeProps) => { - const { comments, isLoadingIds, updateComment } = useUpdateComment(caseData.comments); + const { comments, isLoadingIds, updateComment, addPostedComment } = useUpdateComment( + caseData.comments + ); const [manageMarkdownEditIds, setManangeMardownEditIds] = useState([]); @@ -63,7 +65,10 @@ export const UserActionTree = React.memo( [caseData.description, handleManageMarkdownEditId, manageMarkdownEditIds, onUpdateField] ); - const MarkdownNewComment = useMemo(() => , [caseData.id]); + const MarkdownNewComment = useMemo( + () => , + [caseData.id] + ); return ( <> From cc4d50649ecec58d229bfa49a55df83b3427b35a Mon Sep 17 00:00:00 2001 From: stephmilovic Date: Sat, 7 Mar 2020 12:40:46 -0600 Subject: [PATCH 05/13] action reducer cleanup --- .../components/markdown_editor/form.tsx | 3 ++- .../siem/public/containers/case/constants.ts | 6 ----- .../public/containers/case/use_get_case.tsx | 25 +++++++++--------- .../public/containers/case/use_get_tags.tsx | 24 ++++++++--------- .../public/containers/case/use_post_case.tsx | 21 +++++++-------- .../containers/case/use_update_case.tsx | 26 +++++++++---------- 6 files changed, 47 insertions(+), 58 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/markdown_editor/form.tsx b/x-pack/legacy/plugins/siem/public/components/markdown_editor/form.tsx index 8ba0476fb0e05..3c5287a6fac24 100644 --- a/x-pack/legacy/plugins/siem/public/components/markdown_editor/form.tsx +++ b/x-pack/legacy/plugins/siem/public/components/markdown_editor/form.tsx @@ -5,7 +5,7 @@ */ import { EuiFormRow } from '@elastic/eui'; -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback } from 'react'; import { FieldHook, getFieldValidityAndErrorMessage } from '../../shared_imports'; import { MarkdownEditor } from '.'; @@ -34,6 +34,7 @@ export const MarkdownEditorForm = ({ }, [field] ); + return ( { switch (action.type) { - case FETCH_INIT: + case 'FETCH_INIT': return { ...state, isLoading: true, isError: false, }; - case FETCH_SUCCESS: + case 'FETCH_SUCCESS': return { ...state, isLoading: false, isError: false, - data: getTypedPayload(action.payload), + data: action.payload, }; - case FETCH_FAILURE: + case 'FETCH_FAILURE': return { ...state, isLoading: false, @@ -76,11 +75,11 @@ export const useGetCase = (caseId: string): [CaseState] => { const callFetch = () => { let didCancel = false; const fetchData = async () => { - dispatch({ type: FETCH_INIT }); + dispatch({ type: 'FETCH_INIT' }); try { const response = await getCase(caseId); if (!didCancel) { - dispatch({ type: FETCH_SUCCESS, payload: response }); + dispatch({ type: 'FETCH_SUCCESS', payload: response }); } } catch (error) { if (!didCancel) { @@ -89,7 +88,7 @@ export const useGetCase = (caseId: string): [CaseState] => { error: error.body && error.body.message ? new Error(error.body.message) : error, dispatchToaster, }); - dispatch({ type: FETCH_FAILURE }); + dispatch({ type: 'FETCH_FAILURE' }); } } }; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx index 7d3e00a4f2be4..fbb75fcfc7874 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx @@ -9,7 +9,6 @@ import { useStateToaster } from '../../components/toasters'; import { errorToToaster } from '../../components/ml/api/error_to_toaster'; import { getTags } from './api'; -import { FETCH_FAILURE, FETCH_INIT, FETCH_SUCCESS } from './constants'; import * as i18n from './translations'; interface TagsState { @@ -17,28 +16,27 @@ interface TagsState { isLoading: boolean; isError: boolean; } -interface Action { - type: string; - payload?: string[]; -} +type Action = + | { type: 'FETCH_INIT' } + | { type: 'FETCH_SUCCESS'; payload: string[] } + | { type: 'FETCH_FAILURE' }; const dataFetchReducer = (state: TagsState, action: Action): TagsState => { switch (action.type) { - case FETCH_INIT: + case 'FETCH_INIT': return { ...state, isLoading: true, isError: false, }; - case FETCH_SUCCESS: - const getTypedPayload = (a: Action['payload']) => a as string[]; + case 'FETCH_SUCCESS': return { ...state, isLoading: false, isError: false, - data: getTypedPayload(action.payload), + data: action.payload, }; - case FETCH_FAILURE: + case 'FETCH_FAILURE': return { ...state, isLoading: false, @@ -61,11 +59,11 @@ export const useGetTags = (): [TagsState] => { useEffect(() => { let didCancel = false; const fetchData = async () => { - dispatch({ type: FETCH_INIT }); + dispatch({ type: 'FETCH_INIT' }); try { const response = await getTags(); if (!didCancel) { - dispatch({ type: FETCH_SUCCESS, payload: response }); + dispatch({ type: 'FETCH_SUCCESS', payload: response }); } } catch (error) { if (!didCancel) { @@ -74,7 +72,7 @@ export const useGetTags = (): [TagsState] => { error: error.body && error.body.message ? new Error(error.body.message) : error, dispatchToaster, }); - dispatch({ type: FETCH_FAILURE }); + dispatch({ type: 'FETCH_FAILURE' }); } } }; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.tsx index 7497b30395155..535fc03d663ca 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.tsx @@ -11,7 +11,6 @@ import { useStateToaster } from '../../components/toasters'; import { errorToToaster } from '../../components/ml/api/error_to_toaster'; import { postCase } from './api'; -import { FETCH_FAILURE, FETCH_INIT, FETCH_SUCCESS } from './constants'; import * as i18n from './translations'; import { Case } from './types'; @@ -20,27 +19,27 @@ interface NewCaseState { isLoading: boolean; isError: boolean; } -interface Action { - type: string; - payload?: Case; -} +type Action = + | { type: 'FETCH_INIT' } + | { type: 'FETCH_SUCCESS'; payload: Case } + | { type: 'FETCH_FAILURE' }; const dataFetchReducer = (state: NewCaseState, action: Action): NewCaseState => { switch (action.type) { - case FETCH_INIT: + case 'FETCH_INIT': return { ...state, isLoading: true, isError: false, }; - case FETCH_SUCCESS: + case 'FETCH_SUCCESS': return { ...state, isLoading: false, isError: false, caseData: action.payload ?? null, }; - case FETCH_FAILURE: + case 'FETCH_FAILURE': return { ...state, isLoading: false, @@ -65,11 +64,11 @@ export const usePostCase = (): UsePostCase => { const postMyCase = useCallback(async (data: CaseRequest) => { let cancel = false; try { - dispatch({ type: FETCH_INIT }); + dispatch({ type: 'FETCH_INIT' }); const response = await postCase({ ...data, state: 'open' }); if (!cancel) { dispatch({ - type: FETCH_SUCCESS, + type: 'FETCH_SUCCESS', payload: response, }); } @@ -80,7 +79,7 @@ export const usePostCase = (): UsePostCase => { error: error.body && error.body.message ? new Error(error.body.message) : error, dispatchToaster, }); - dispatch({ type: FETCH_FAILURE }); + dispatch({ type: 'FETCH_FAILURE' }); } } return () => { diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx index 21c8fb5dc7032..95b08554a13b5 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx @@ -11,10 +11,8 @@ import { useStateToaster } from '../../components/toasters'; import { errorToToaster } from '../../components/ml/api/error_to_toaster'; import { patchCase } from './api'; -import { FETCH_FAILURE, FETCH_INIT, FETCH_SUCCESS } from './constants'; import * as i18n from './translations'; import { Case } from './types'; -import { getTypedPayload } from './utils'; type UpdateKey = keyof CaseRequest; @@ -30,30 +28,30 @@ export interface UpdateByKey { updateValue: CaseRequest[UpdateKey]; } -interface Action { - type: string; - payload?: Case | UpdateKey; -} +type Action = + | { type: 'FETCH_INIT'; payload: UpdateKey } + | { type: 'FETCH_SUCCESS'; payload: Case } + | { type: 'FETCH_FAILURE' }; const dataFetchReducer = (state: NewCaseState, action: Action): NewCaseState => { switch (action.type) { - case FETCH_INIT: + case 'FETCH_INIT': return { ...state, isLoading: true, isError: false, - updateKey: getTypedPayload(action.payload), + updateKey: action.payload, }; - case FETCH_SUCCESS: + case 'FETCH_SUCCESS': return { ...state, isLoading: false, isError: false, - caseData: getTypedPayload(action.payload), + caseData: action.payload, updateKey: null, }; - case FETCH_FAILURE: + case 'FETCH_FAILURE': return { ...state, isLoading: false, @@ -81,14 +79,14 @@ export const useUpdateCase = (caseId: string, initialData: Case): UseUpdateCase async ({ updateKey, updateValue }: UpdateByKey) => { let cancel = false; try { - dispatch({ type: FETCH_INIT, payload: updateKey }); + dispatch({ type: 'FETCH_INIT', payload: updateKey }); const response = await patchCase( caseId, { [updateKey]: updateValue }, state.caseData.version ); if (!cancel) { - dispatch({ type: FETCH_SUCCESS, payload: response }); + dispatch({ type: 'FETCH_SUCCESS', payload: response }); } } catch (error) { if (!cancel) { @@ -97,7 +95,7 @@ export const useUpdateCase = (caseId: string, initialData: Case): UseUpdateCase error: error.body && error.body.message ? new Error(error.body.message) : error, dispatchToaster, }); - dispatch({ type: FETCH_FAILURE }); + dispatch({ type: 'FETCH_FAILURE' }); } } return () => { From f2abf9273e32e687e76c031834c751c33cda992d Mon Sep 17 00:00:00 2001 From: stephmilovic Date: Sat, 7 Mar 2020 12:50:18 -0600 Subject: [PATCH 06/13] more cleanups --- .../siem/public/containers/case/use_get_case.tsx | 4 ++-- .../siem/public/containers/case/use_get_tags.tsx | 10 +++++----- .../public/pages/case/components/all_cases/actions.tsx | 2 +- .../pages/case/components/all_cases/table_filters.tsx | 4 ++-- .../public/pages/case/components/case_view/index.tsx | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx index a876bfc92fd19..644784cc1341b 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx @@ -64,7 +64,7 @@ const initialData: Case = { version: '', }; -export const useGetCase = (caseId: string): [CaseState] => { +export const useGetCase = (caseId: string): CaseState => { const [state, dispatch] = useReducer(dataFetchReducer, { isLoading: true, isError: false, @@ -101,5 +101,5 @@ export const useGetCase = (caseId: string): [CaseState] => { useEffect(() => { callFetch(); }, [caseId]); - return [state]; + return state; }; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx index fbb75fcfc7874..633f6e2f38941 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx @@ -12,7 +12,7 @@ import { getTags } from './api'; import * as i18n from './translations'; interface TagsState { - data: string[]; + tags: string[]; isLoading: boolean; isError: boolean; } @@ -34,7 +34,7 @@ const dataFetchReducer = (state: TagsState, action: Action): TagsState => { ...state, isLoading: false, isError: false, - data: action.payload, + tags: action.payload, }; case 'FETCH_FAILURE': return { @@ -48,11 +48,11 @@ const dataFetchReducer = (state: TagsState, action: Action): TagsState => { }; const initialData: string[] = []; -export const useGetTags = (): [TagsState] => { +export const useGetTags = (): TagsState => { const [state, dispatch] = useReducer(dataFetchReducer, { isLoading: false, isError: false, - data: initialData, + tags: initialData, }); const [, dispatchToaster] = useStateToaster(); @@ -81,5 +81,5 @@ export const useGetTags = (): [TagsState] => { didCancel = true; }; }, []); - return [state]; + return state; }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/actions.tsx index 1042ff6970c74..33a1953b9d2f8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/actions.tsx @@ -25,7 +25,7 @@ export const getActions = ({ description: i18n.DELETE, icon: 'trash', name: i18n.DELETE, - onClick: (theCase: Case) => deleteCaseOnClick(theCase), + onClick: deleteCaseOnClick, type: 'icon', 'data-test-subj': 'action-delete', }, diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.tsx index 5256fb6d7b3ee..9356577fd1888 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.tsx @@ -38,7 +38,7 @@ const CasesTableFiltersComponent = ({ const [search, setSearch] = useState(initial.search); const [selectedTags, setSelectedTags] = useState(initial.tags); const [showOpenCases, setShowOpenCases] = useState(initial.state === 'open'); - const [{ data }] = useGetTags(); + const { tags } = useGetTags(); const handleSelectedTags = useCallback( newTags => { @@ -106,7 +106,7 @@ const CasesTableFiltersComponent = ({ buttonLabel={i18n.TAGS} onSelectedOptionsChanged={handleSelectedTags} selectedOptions={selectedTags} - options={data} + options={tags} optionsEmptyLabel={i18n.NO_TAGS_AVAILABLE} /> diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx index 5dc7407690986..096be02035bef 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -255,7 +255,7 @@ export const CaseComponent = React.memo(({ caseId, initialData }) => }); export const CaseView = React.memo(({ caseId }: Props) => { - const [{ data, isLoading, isError }] = useGetCase(caseId); + const { data, isLoading, isError } = useGetCase(caseId); if (isError) { return null; } From 7e98271bb49de9ea80f3ff73ed432d2d19aab454 Mon Sep 17 00:00:00 2001 From: stephmilovic Date: Mon, 9 Mar 2020 08:54:42 -0500 Subject: [PATCH 07/13] cleaning --- .../plugins/siem/public/containers/case/use_delete_cases.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx index bdcf129d0941e..75c294c35ef61 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx @@ -21,7 +21,7 @@ type Action = | { type: 'FETCH_INIT' } | { type: 'FETCH_SUCCESS'; payload: boolean } | { type: 'FETCH_FAILURE' } - | { type: 'RESET_IS_DELETED'; payload: boolean }; + | { type: 'RESET_IS_DELETED' }; const dataFetchReducer = (state: DeleteState, action: Action): DeleteState => { switch (action.type) { @@ -101,7 +101,7 @@ export const useDeleteCases = (): UseDeleteCase => { }, [state.isDisplayConfirmDeleteModal]); const dispatchResetIsDeleted = useCallback(() => { - dispatch({ type: 'RESET_IS_DELETED', payload: false }); + dispatch({ type: 'RESET_IS_DELETED' }); }, [state.isDisplayConfirmDeleteModal]); const handleOnDeleteConfirm = useCallback( From b7ce857c1d677433888eecb77e4d49d9d4c8cd8a Mon Sep 17 00:00:00 2001 From: stephmilovic Date: Mon, 9 Mar 2020 10:24:41 -0500 Subject: [PATCH 08/13] bulk delete --- .../containers/case/use_delete_cases.tsx | 2 +- .../pages/case/components/all_cases/index.tsx | 58 +++++++++++++------ .../case/components/all_cases/translations.ts | 5 +- .../case/components/bulk_actions/index.tsx | 20 +++---- .../components/confirm_delete_case/index.tsx | 6 +- .../confirm_delete_case/translations.ts | 13 +++++ 6 files changed, 68 insertions(+), 36 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx index 75c294c35ef61..3844c23056fc6 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx @@ -67,7 +67,7 @@ interface UseDeleteCase extends DeleteState { export const useDeleteCases = (): UseDeleteCase => { const [state, dispatch] = useReducer(dataFetchReducer, { isDisplayConfirmDeleteModal: false, - isLoading: true, + isLoading: false, isError: false, isDeleted: false, }); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx index 26cb341026b42..e596b537a4e80 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx @@ -96,6 +96,7 @@ export const AllCases = React.memo(() => { dispatchResetIsDeleted, handleOnDeleteConfirm, handleToggleModal, + isLoading: isDeleting, isDeleted, isDisplayConfirmDeleteModal, } = useDeleteCases(); @@ -111,16 +112,21 @@ export const AllCases = React.memo(() => { title: '', id: '', }); + const [deleteBulk, setDeleteBulk] = useState([]); const confirmDeleteModal = useMemo( () => ( 0} onCancel={handleToggleModal} - onConfirm={handleOnDeleteConfirm.bind(null, [deleteThisCase.id])} + onConfirm={handleOnDeleteConfirm.bind( + null, + deleteBulk.length > 0 ? deleteBulk : [deleteThisCase.id] + )} /> ), - [deleteThisCase, isDisplayConfirmDeleteModal] + [deleteBulk, deleteThisCase, isDisplayConfirmDeleteModal] ); const toggleDeleteModal = useCallback( @@ -131,6 +137,33 @@ export const AllCases = React.memo(() => { [isDisplayConfirmDeleteModal] ); + const toggleBulkDeleteModal = useCallback( + (deleteCases: string[]) => { + handleToggleModal(); + setDeleteBulk(deleteCases); + }, + [isDisplayConfirmDeleteModal] + ); + + const selectedCaseIds = useMemo( + (): string[] => + selectedCases.reduce((arr: string[], caseObj: Case) => [...arr, caseObj.id], []), + [selectedCases] + ); + + const getBulkItemsPopoverContent = useCallback( + (closePopover: () => void) => ( + + ), + [selectedCaseIds, filterOptions.state] + ); const actions = useMemo( () => getActions({ @@ -138,7 +171,7 @@ export const AllCases = React.memo(() => { deleteCaseOnClick: toggleDeleteModal, dispatchUpdate: dispatchUpdateCaseProperty, }), - [filterOptions.state, dispatchUpdateCaseProperty] + [filterOptions.state] ); const tableOnChangeCallback = useCallback( @@ -181,19 +214,6 @@ export const AllCases = React.memo(() => { [data, queryParams] ); - const getBulkItemsPopoverContent = useCallback( - (closePopover: () => void) => ( - - ), - [selectedCases, filterOptions.state] - ); - const sorting: EuiTableSortingType = { sort: { field: queryParams.sortField, direction: queryParams.sortOrder }, }; @@ -243,7 +263,9 @@ export const AllCases = React.memo(() => { - {isCasesLoading && !isDataEmpty && } + {(isCasesLoading || isDeleting) && !isDataEmpty && ( + + )} { - {i18n.SELECTED_CASES(selectedCases.length)} + {i18n.SHOWING_SELECTED_CASES(selectedCases.length)} +export const SHOWING_SELECTED_CASES = (totalRules: number) => i18n.translate('xpack.siem.case.caseTable.selectedCasesTitle', { values: { totalRules }, defaultMessage: 'Selected {totalRules} {totalRules, plural, =1 {case} other {cases}}', @@ -66,6 +66,3 @@ export const REOPEN_CASE = i18n.translate('xpack.siem.case.caseTable.reopenCase' export const CLOSE_CASE = i18n.translate('xpack.siem.case.caseTable.closeCase', { defaultMessage: 'Close case', }); -export const DUPLICATE_CASE = i18n.translate('xpack.siem.case.caseTable.duplicateCase', { - defaultMessage: 'Duplicate case', -}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/bulk_actions/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/bulk_actions/index.tsx index 2fe25a7d1f5d0..6f6dc993085bf 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/bulk_actions/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/bulk_actions/index.tsx @@ -5,28 +5,29 @@ */ import { EuiContextMenuItem } from '@elastic/eui'; -import React from 'react'; +import React, { useMemo } from 'react'; import * as i18n from './translations'; import { Case } from '../../../../containers/case/types'; interface GetBulkItems { // cases: Case[]; closePopover: () => void; + deleteCasesAction: (cases: string[]) => void; // dispatch: Dispatch; // dispatchToaster: Dispatch; // reFetchCases: (refreshPrePackagedCase?: boolean) => void; - selectedCases: Case[]; + selectedCaseIds: string[]; caseStatus: string; } export const getBulkItems = ({ - // cases, + deleteCasesAction, closePopover, caseStatus, // dispatch, // dispatchToaster, // reFetchCases, - selectedCases, + selectedCaseIds, }: GetBulkItems) => { return [ caseStatus === 'open' ? ( @@ -36,8 +37,6 @@ export const getBulkItems = ({ disabled={true} // TO DO onClick={async () => { closePopover(); - // await deleteCasesAction(selectedCases, dispatch, dispatchToaster); - // reFetchCases(true); }} > {i18n.BULK_ACTION_CLOSE_SELECTED} @@ -47,9 +46,10 @@ export const getBulkItems = ({ key={i18n.BULK_ACTION_OPEN_SELECTED} icon="magnet" disabled={true} // TO DO - onClick={async () => { + onClick={() => { closePopover(); - // await deleteCasesAction(selectedCases, dispatch, dispatchToaster); + + // deleteCasesAction(selectedCaseIds); // reFetchCases(true); }} > @@ -59,11 +59,9 @@ export const getBulkItems = ({ { closePopover(); - // await deleteCasesAction(selectedCases, dispatch, dispatchToaster); - // reFetchCases(true); + deleteCasesAction(selectedCaseIds); }} > {i18n.BULK_ACTION_DELETE_SELECTED} diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/index.tsx index a984895458f0f..f3b014661e2d1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/index.tsx @@ -11,6 +11,7 @@ import * as i18n from './translations'; interface ConfirmDeleteCaseModalProps { caseTitle: string; isModalVisible: boolean; + isPlural: boolean; onCancel: () => void; onConfirm: () => void; } @@ -18,6 +19,7 @@ interface ConfirmDeleteCaseModalProps { const ConfirmDeleteCaseModalComp: React.FC = ({ caseTitle, isModalVisible, + isPlural, onCancel, onConfirm, }) => { @@ -33,9 +35,9 @@ const ConfirmDeleteCaseModalComp: React.FC = ({ defaultFocusedButton="confirm" onCancel={onCancel} onConfirm={onConfirm} - title={i18n.DELETE_TITLE(caseTitle)} + title={isPlural ? i18n.DELETE_SELECTED_CASES : i18n.DELETE_TITLE(caseTitle)} > - {i18n.CONFIRM_QUESTION} + {isPlural ? i18n.CONFIRM_QUESTION_PLURAL : i18n.CONFIRM_QUESTION} ); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/translations.ts index c933e2caf63a2..1999eb6f4cb23 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/translations.ts @@ -20,3 +20,16 @@ export const CONFIRM_QUESTION = i18n.translate( 'By deleting this case, all related case data will be permanently removed and you will no longer be able to push data to a third-party case management system. Are you sure you wish to proceed?', } ); +export const DELETE_SELECTED_CASES = i18n.translate( + 'xpack.siem.case.confirmDeleteCase.selectedCases', + { + defaultMessage: 'Delete selected cases', + } +); + +export const CONFIRM_QUESTION_PLURAL = (totalCases: number) => + i18n.translate('xpack.siem.case.confirmDeleteCase.confirmQuestion', { + values: { totalCases }, + defaultMessage: + 'By deleting these cases, all related case data will be permanently removed and you will no longer be able to push data to a third-party case management system. Are you sure you wish to proceed?', + }); From 9b27a3b8295fd33ba3a42fd680c8938e6bedd28c Mon Sep 17 00:00:00 2001 From: stephmilovic Date: Mon, 9 Mar 2020 12:53:56 -0500 Subject: [PATCH 09/13] clean up --- .../plugins/siem/public/containers/case/api.ts | 12 ++++++++++++ .../components/confirm_delete_case/translations.ts | 9 +++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/containers/case/api.ts b/x-pack/legacy/plugins/siem/public/containers/case/api.ts index f3c86b36f8869..032630eff7ea3 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/api.ts @@ -140,3 +140,15 @@ export const deleteCases = async (caseIds: string[]): Promise => { await throwIfNotOk(response.response); return response.body === 'true' ? true : false; }; + +export const deleteComment = async (caseId: string, commentId: string): Promise => { + const response = await KibanaServices.get().http.fetch( + `${CASES_URL}/${caseId}/comments/${commentId}`, + { + method: 'DELETE', + asResponse: true, + } + ); + await throwIfNotOk(response.response); + return response.body === 'true' ? true : false; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/translations.ts index 1999eb6f4cb23..06e940c60d0a1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/translations.ts @@ -27,9 +27,10 @@ export const DELETE_SELECTED_CASES = i18n.translate( } ); -export const CONFIRM_QUESTION_PLURAL = (totalCases: number) => - i18n.translate('xpack.siem.case.confirmDeleteCase.confirmQuestion', { - values: { totalCases }, +export const CONFIRM_QUESTION_PLURAL = i18n.translate( + 'xpack.siem.case.confirmDeleteCase.confirmQuestionPlural', + { defaultMessage: 'By deleting these cases, all related case data will be permanently removed and you will no longer be able to push data to a third-party case management system. Are you sure you wish to proceed?', - }); + } +); From 48d49ff61fd631b633bdf97044f3944ee06bfe75 Mon Sep 17 00:00:00 2001 From: stephmilovic Date: Mon, 9 Mar 2020 13:17:11 -0500 Subject: [PATCH 10/13] fix plural on button text --- .../pages/case/components/confirm_delete_case/index.tsx | 2 +- x-pack/legacy/plugins/siem/public/pages/case/translations.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/index.tsx index f3b014661e2d1..dff36a6dac571 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/confirm_delete_case/index.tsx @@ -31,7 +31,7 @@ const ConfirmDeleteCaseModalComp: React.FC = ({ Date: Mon, 9 Mar 2020 15:07:17 -0500 Subject: [PATCH 11/13] fix tests and types --- .../case/components/all_cases/__mock__/index.tsx | 5 +++++ .../pages/case/components/all_cases/index.test.tsx | 14 ++++++++++---- .../pages/case/components/bulk_actions/index.tsx | 13 +------------ .../case/components/case_view/__mock__/index.tsx | 2 ++ .../pages/case/components/case_view/index.tsx | 1 + 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx index 2e57e5f2f95d9..bc6dfe4af25ff 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx @@ -14,6 +14,7 @@ export const useGetCasesMockState: UseGetCasesState = { id: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15', createdAt: '2020-02-13T19:44:23.627Z', createdBy: { username: 'elastic' }, + commentIds: [], comments: [], description: 'Security banana Issue', state: 'open', @@ -26,6 +27,7 @@ export const useGetCasesMockState: UseGetCasesState = { id: '362a5c10-4e99-11ea-9290-35d05cb55c15', createdAt: '2020-02-13T19:44:13.328Z', createdBy: { username: 'elastic' }, + commentIds: [], comments: [], description: 'Security banana Issue', state: 'open', @@ -38,6 +40,7 @@ export const useGetCasesMockState: UseGetCasesState = { id: '34f8b9e0-4e99-11ea-9290-35d05cb55c15', createdAt: '2020-02-13T19:44:11.328Z', createdBy: { username: 'elastic' }, + commentIds: [], comments: [], description: 'Security banana Issue', state: 'open', @@ -50,6 +53,7 @@ export const useGetCasesMockState: UseGetCasesState = { id: '31890e90-4e99-11ea-9290-35d05cb55c15', createdAt: '2020-02-13T19:44:05.563Z', createdBy: { username: 'elastic' }, + commentIds: [], comments: [], description: 'Security banana Issue', state: 'closed', @@ -62,6 +66,7 @@ export const useGetCasesMockState: UseGetCasesState = { id: '2f5b3210-4e99-11ea-9290-35d05cb55c15', createdAt: '2020-02-13T19:44:01.901Z', createdBy: { username: 'elastic' }, + commentIds: [], comments: [], description: 'Security banana Issue', state: 'open', diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx index 40a76c636954f..10786940eee7f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx @@ -11,31 +11,36 @@ import { AllCases } from './'; import { TestProviders } from '../../../../mock'; import { useGetCasesMockState } from './__mock__'; import * as apiHook from '../../../../containers/case/use_get_cases'; +import { act } from '@testing-library/react'; +import { wait } from '../../../../lib/helpers'; describe('AllCases', () => { + const dispatchUpdateCaseProperty = jest.fn(); + const getCaseCount = jest.fn(); + const refetchCases = jest.fn(); const setFilters = jest.fn(); const setQueryParams = jest.fn(); const setSelectedCases = jest.fn(); - const getCaseCount = jest.fn(); - const dispatchUpdateCaseProperty = jest.fn(); beforeEach(() => { jest.resetAllMocks(); jest.spyOn(apiHook, 'useGetCases').mockReturnValue({ ...useGetCasesMockState, dispatchUpdateCaseProperty, getCaseCount, + refetchCases, setFilters, setQueryParams, setSelectedCases, }); moment.tz.setDefault('UTC'); }); - it('should render AllCases', () => { + it('should render AllCases', async () => { const wrapper = mount( ); + await act(() => wait()); expect( wrapper .find(`a[data-test-subj="case-details-link"]`) @@ -73,12 +78,13 @@ describe('AllCases', () => { .text() ).toEqual('Showing 10 cases'); }); - it('should tableHeaderSortButton AllCases', () => { + it('should tableHeaderSortButton AllCases', async () => { const wrapper = mount( ); + await act(() => wait()); wrapper .find('[data-test-subj="tableHeaderSortButton"]') .first() diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/bulk_actions/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/bulk_actions/index.tsx index 6f6dc993085bf..f171ebf91b787 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/bulk_actions/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/bulk_actions/index.tsx @@ -4,18 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; import { EuiContextMenuItem } from '@elastic/eui'; -import React, { useMemo } from 'react'; import * as i18n from './translations'; -import { Case } from '../../../../containers/case/types'; interface GetBulkItems { - // cases: Case[]; closePopover: () => void; deleteCasesAction: (cases: string[]) => void; - // dispatch: Dispatch; - // dispatchToaster: Dispatch; - // reFetchCases: (refreshPrePackagedCase?: boolean) => void; selectedCaseIds: string[]; caseStatus: string; } @@ -24,9 +19,6 @@ export const getBulkItems = ({ deleteCasesAction, closePopover, caseStatus, - // dispatch, - // dispatchToaster, - // reFetchCases, selectedCaseIds, }: GetBulkItems) => { return [ @@ -48,9 +40,6 @@ export const getBulkItems = ({ disabled={true} // TO DO onClick={() => { closePopover(); - - // deleteCasesAction(selectedCaseIds); - // reFetchCases(true); }} > {i18n.BULK_ACTION_OPEN_SELECTED} diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx index c2d3cae6774b0..3875c316e80d1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx @@ -11,6 +11,7 @@ export const caseProps: CaseProps = { caseId: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15', initialData: { id: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15', + commentIds: ['a357c6a0-5435-11ea-b427-fb51a1fcb7b8'], comments: [ { comment: 'Solve this fast!', @@ -37,6 +38,7 @@ export const caseProps: CaseProps = { export const data: Case = { id: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15', + commentIds: ['a357c6a0-5435-11ea-b427-fb51a1fcb7b8'], comments: [ { comment: 'Solve this fast!', diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx index 096be02035bef..080cbdc143593 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -129,6 +129,7 @@ export const CaseComponent = React.memo(({ caseId, initialData }) => From 9965750cb324a1ea0e01a9d1734b868a0f3f3be4 Mon Sep 17 00:00:00 2001 From: stephmilovic Date: Tue, 10 Mar 2020 09:58:37 -0600 Subject: [PATCH 12/13] fixing --- .../legacy/plugins/siem/public/containers/case/api.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/containers/case/api.ts b/x-pack/legacy/plugins/siem/public/containers/case/api.ts index b68bffd7dbff7..4c0e83a6eb673 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/api.ts @@ -123,13 +123,3 @@ export const deleteCases = async (caseIds: string[]): Promise => { }); return response === 'true' ? true : false; }; - -export const deleteComment = async (caseId: string, commentId: string): Promise => { - const response = await KibanaServices.get().http.fetch( - `${CASES_URL}/${caseId}/comments/${commentId}`, - { - method: 'DELETE', - } - ); - return response === 'true' ? true : false; -}; From ff6ff36d15e18bdfd1aba030eade7cebbef3f2c6 Mon Sep 17 00:00:00 2001 From: stephmilovic Date: Tue, 10 Mar 2020 13:23:34 -0600 Subject: [PATCH 13/13] pr changes --- .../containers/case/use_delete_cases.tsx | 37 ++++++++++--------- .../public/containers/case/use_get_case.tsx | 2 +- .../public/containers/case/use_get_cases.tsx | 2 +- .../public/containers/case/use_get_tags.tsx | 2 +- .../public/containers/case/use_post_case.tsx | 2 +- .../containers/case/use_post_comment.tsx | 2 +- .../containers/case/use_update_case.tsx | 2 +- .../containers/case/use_update_comment.tsx | 2 +- .../pages/case/components/all_cases/index.tsx | 10 ++--- 9 files changed, 31 insertions(+), 30 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx index 07a7955af7126..d5a3b3cf9314c 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx @@ -54,7 +54,7 @@ const dataFetchReducer = (state: DeleteState, action: Action): DeleteState => { isDeleted: false, }; default: - throw new Error(); + return state; } }; interface UseDeleteCase extends DeleteState { @@ -72,24 +72,27 @@ export const useDeleteCases = (): UseDeleteCase => { }); const [, dispatchToaster] = useStateToaster(); - const dispatchDeleteCases = useCallback(async (caseIds: string[]) => { + const dispatchDeleteCases = useCallback((caseIds: string[]) => { let cancel = false; - try { - dispatch({ type: 'FETCH_INIT' }); - await deleteCases(caseIds); - if (!cancel) { - dispatch({ type: 'FETCH_SUCCESS', payload: true }); + const deleteData = async () => { + try { + dispatch({ type: 'FETCH_INIT' }); + await deleteCases(caseIds); + if (!cancel) { + dispatch({ type: 'FETCH_SUCCESS', payload: true }); + } + } catch (error) { + if (!cancel) { + errorToToaster({ + title: i18n.ERROR_TITLE, + error: error.body && error.body.message ? new Error(error.body.message) : error, + dispatchToaster, + }); + dispatch({ type: 'FETCH_FAILURE' }); + } } - } catch (error) { - if (!cancel) { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); - dispatch({ type: 'FETCH_FAILURE' }); - } - } + }; + deleteData(); return () => { cancel = true; }; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx index 731b9c68205a5..6020969ed6375 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx @@ -44,7 +44,7 @@ const dataFetchReducer = (state: CaseState, action: Action): CaseState => { isError: true, }; default: - throw new Error(); + return state; } }; const initialData: Case = { diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx index bdeb18fab72eb..1c7c30ae9da18 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx @@ -96,7 +96,7 @@ const dataFetchReducer = (state: UseGetCasesState, action: Action): UseGetCasesS selectedCases: action.payload, }; default: - throw new Error(); + return state; } }; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx index bd72b7d77f91a..e3657f5b09da9 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx @@ -42,7 +42,7 @@ const dataFetchReducer = (state: TagsState, action: Action): TagsState => { isError: true, }; default: - throw new Error(); + return state; } }; const initialData: string[] = []; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.tsx index bbc252fe1a885..14b9e78846906 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.tsx @@ -44,7 +44,7 @@ const dataFetchReducer = (state: NewCaseState, action: Action): NewCaseState => isError: true, }; default: - throw new Error(); + return state; } }; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.tsx index 98d5751447d15..a96cb97d7cc7b 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.tsx @@ -52,7 +52,7 @@ const dataFetchReducer = (state: NewCommentState, action: Action): NewCommentSta isError: true, }; default: - throw new Error(); + return state; } }; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx index c2d5f33957c19..2b1081b9b901c 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx @@ -58,7 +58,7 @@ const dataFetchReducer = (state: NewCaseState, action: Action): NewCaseState => updateKey: null, }; default: - throw new Error(); + return state; } }; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx index 518ad87fc0eba..a40a1100ca735 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx @@ -66,7 +66,7 @@ const dataFetchReducer = (state: CommentUpdateState, action: Action): CommentUpd isError: true, }; default: - throw new Error(); + return state; } }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx index e596b537a4e80..1d22f6a246960 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx @@ -59,11 +59,9 @@ const FlexItemDivider = styled(EuiFlexItem)` const ProgressLoader = styled(EuiProgress)` ${({ theme }) => css` - .euiFlexGroup--gutterMedium > &.euiFlexItem { - top: 2px; - border-radius: ${theme.eui.euiBorderRadius}; - z-index: ${theme.eui.euiZHeader}; - } + top: 2px; + border-radius: ${theme.eui.euiBorderRadius}; + z-index: ${theme.eui.euiZHeader}; `} `; @@ -264,7 +262,7 @@ export const AllCases = React.memo(() => { {(isCasesLoading || isDeleting) && !isDataEmpty && ( - + )}