-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Security Solution][Detection & Response] 131028 implement recently o…
…pened cases table (#131029) * First draft at possible implementation. tests need to be added, and imports,comments, logs cleaned up * Further tweaks to alerts counters * Add tests for alerts_counters hook * Working on vulnerable hosts * Add useVulnerableHostsCounters hook along with tests * add Vulnerable users and tests * Move files to components folder and wire up to detections overview page * Add translations * add querytoggle and navigation to both tables * fix bug for toggleQuery * update button navigation * remove alerts by status, as Angela built instead * Working on changing test files * test files for host and user hooks complete * Components complete * bug fixes from PR * failing tests * Undo bad edit to useRuleAlerts test * Fix show inspect on hover, and use HostDetailsLink component * missed in last commit * more fixes from PR review * recent cases table working, need tests * first pass for table and data fetching * Make changes from PR review * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * PR fixes * PR fixes * remove cases api date workaround * enable detectionsResponse for deployed instance * Fix tests * turn off detectionresponseEnabled flag * fixes from design review * stability fix. remove useUserInfo * Add comment for removing togglequery code Co-authored-by: Kristof-Pierre Cummings <kristofpierre.cummings@elastic.co> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
- Loading branch information
1 parent
a632484
commit c43a51d
Showing
18 changed files
with
881 additions
and
56 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
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
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
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
104 changes: 104 additions & 0 deletions
104
...y_solution/public/overview/components/detection_response/cases_table/cases_table.test.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,104 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import React from 'react'; | ||
|
||
import { render } from '@testing-library/react'; | ||
|
||
import { TestProviders } from '../../../../common/mock'; | ||
import { parsedCasesItems } from './mock_data'; | ||
import { CasesTable } from './cases_table'; | ||
import type { UseCaseItems } from './use_case_items'; | ||
|
||
const mockGetAppUrl = jest.fn(); | ||
jest.mock('../../../../common/lib/kibana/hooks', () => { | ||
const original = jest.requireActual('../../../../common/lib/kibana/hooks'); | ||
return { | ||
...original, | ||
useNavigation: () => ({ | ||
getAppUrl: mockGetAppUrl, | ||
}), | ||
}; | ||
}); | ||
|
||
type UseCaseItemsReturn = ReturnType<UseCaseItems>; | ||
const defaultCaseItemsReturn: UseCaseItemsReturn = { | ||
items: [], | ||
isLoading: false, | ||
updatedAt: Date.now(), | ||
}; | ||
const mockUseCaseItems = jest.fn(() => defaultCaseItemsReturn); | ||
const mockUseCaseItemsReturn = (overrides: Partial<UseCaseItemsReturn>) => { | ||
mockUseCaseItems.mockReturnValueOnce({ | ||
...defaultCaseItemsReturn, | ||
...overrides, | ||
}); | ||
}; | ||
|
||
jest.mock('./use_case_items', () => ({ | ||
useCaseItems: () => mockUseCaseItems(), | ||
})); | ||
|
||
const renderComponent = () => | ||
render( | ||
<TestProviders> | ||
<CasesTable /> | ||
</TestProviders> | ||
); | ||
|
||
describe('CasesTable', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('should render empty table', () => { | ||
const { getByText, getByTestId } = renderComponent(); | ||
|
||
expect(getByTestId('recentlyCreatedCasesPanel')).toBeInTheDocument(); | ||
expect(getByText('No cases to display')).toBeInTheDocument(); | ||
expect(getByTestId('allCasesButton')).toBeInTheDocument(); | ||
}); | ||
|
||
it('should render a loading table', () => { | ||
mockUseCaseItemsReturn({ isLoading: true }); | ||
const { getByText, getByTestId } = renderComponent(); | ||
|
||
expect(getByText('Updating...')).toBeInTheDocument(); | ||
expect(getByTestId('allCasesButton')).toBeInTheDocument(); | ||
expect(getByTestId('recentlyCreatedCasesTable')).toHaveClass('euiBasicTable-loading'); | ||
}); | ||
|
||
it('should render the updated at subtitle', () => { | ||
mockUseCaseItemsReturn({ isLoading: false }); | ||
const { getByText } = renderComponent(); | ||
|
||
expect(getByText('Updated now')).toBeInTheDocument(); | ||
}); | ||
|
||
it('should render the table columns', () => { | ||
mockUseCaseItemsReturn({ items: parsedCasesItems }); | ||
const { getAllByRole } = renderComponent(); | ||
|
||
const columnHeaders = getAllByRole('columnheader'); | ||
expect(columnHeaders.at(0)).toHaveTextContent('Name'); | ||
expect(columnHeaders.at(1)).toHaveTextContent('Note'); | ||
expect(columnHeaders.at(2)).toHaveTextContent('Time'); | ||
expect(columnHeaders.at(3)).toHaveTextContent('Created by'); | ||
expect(columnHeaders.at(4)).toHaveTextContent('Status'); | ||
}); | ||
|
||
it('should render the table items', () => { | ||
mockUseCaseItemsReturn({ items: [parsedCasesItems[0]] }); | ||
const { getByTestId } = renderComponent(); | ||
|
||
expect(getByTestId('recentlyCreatedCaseName')).toHaveTextContent('sdcsd'); | ||
expect(getByTestId('recentlyCreatedCaseNote')).toHaveTextContent('klklk'); | ||
expect(getByTestId('recentlyCreatedCaseTime')).toHaveTextContent('April 25, 2022'); | ||
expect(getByTestId('recentlyCreatedCaseCreatedBy')).toHaveTextContent('elastic'); | ||
expect(getByTestId('recentlyCreatedCaseStatus')).toHaveTextContent('Open'); | ||
}); | ||
}); |
145 changes: 145 additions & 0 deletions
145
...curity_solution/public/overview/components/detection_response/cases_table/cases_table.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,145 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import React, { useCallback, useMemo } from 'react'; | ||
|
||
import { | ||
EuiBasicTable, | ||
EuiBasicTableColumn, | ||
EuiButton, | ||
EuiEmptyPrompt, | ||
EuiPanel, | ||
EuiSpacer, | ||
EuiText, | ||
} from '@elastic/eui'; | ||
import { CaseStatuses } from '@kbn/cases-plugin/common'; | ||
|
||
import { SecurityPageName } from '../../../../app/types'; | ||
import { FormattedDate } from '../../../../common/components/formatted_date'; | ||
import { HeaderSection } from '../../../../common/components/header_section'; | ||
import { HoverVisibilityContainer } from '../../../../common/components/hover_visibility_container'; | ||
import { BUTTON_CLASS as INPECT_BUTTON_CLASS } from '../../../../common/components/inspect'; | ||
import { CaseDetailsLink } from '../../../../common/components/links'; | ||
import { useQueryToggle } from '../../../../common/containers/query_toggle'; | ||
import { useNavigation, NavigateTo, GetAppUrl } from '../../../../common/lib/kibana'; | ||
import * as i18n from '../translations'; | ||
import { LastUpdatedAt } from '../util'; | ||
import { StatusBadge } from './status_badge'; | ||
import { CaseItem, useCaseItems } from './use_case_items'; | ||
|
||
type GetTableColumns = (params: { | ||
getAppUrl: GetAppUrl; | ||
navigateTo: NavigateTo; | ||
}) => Array<EuiBasicTableColumn<CaseItem>>; | ||
|
||
const DETECTION_RESPONSE_RECENT_CASES_QUERY_ID = 'recentlyCreatedCasesQuery'; | ||
|
||
export const CasesTable = React.memo(() => { | ||
const { getAppUrl, navigateTo } = useNavigation(); | ||
const { toggleStatus, setToggleStatus } = useQueryToggle( | ||
DETECTION_RESPONSE_RECENT_CASES_QUERY_ID | ||
); | ||
const { items, isLoading, updatedAt } = useCaseItems({ | ||
skip: !toggleStatus, | ||
}); | ||
|
||
const navigateToCases = useCallback(() => { | ||
navigateTo({ deepLinkId: SecurityPageName.case }); | ||
}, [navigateTo]); | ||
|
||
const columns = useMemo( | ||
() => getTableColumns({ getAppUrl, navigateTo }), | ||
[getAppUrl, navigateTo] | ||
); | ||
|
||
return ( | ||
<HoverVisibilityContainer show={true} targetClassNames={[INPECT_BUTTON_CLASS]}> | ||
<EuiPanel hasBorder data-test-subj="recentlyCreatedCasesPanel"> | ||
<HeaderSection | ||
id={DETECTION_RESPONSE_RECENT_CASES_QUERY_ID} | ||
title={i18n.CASES_TABLE_SECTION_TITLE} | ||
titleSize="s" | ||
toggleStatus={toggleStatus} | ||
toggleQuery={setToggleStatus} | ||
subtitle={<LastUpdatedAt updatedAt={updatedAt} isUpdating={isLoading} />} | ||
showInspectButton={false} | ||
/> | ||
|
||
{toggleStatus && ( | ||
<> | ||
<EuiBasicTable | ||
data-test-subj="recentlyCreatedCasesTable" | ||
columns={columns} | ||
items={items} | ||
loading={isLoading} | ||
noItemsMessage={ | ||
<EuiEmptyPrompt title={<h3>{i18n.NO_CASES_FOUND}</h3>} titleSize="xs" /> | ||
} | ||
/> | ||
<EuiSpacer size="m" /> | ||
<EuiButton data-test-subj="allCasesButton" onClick={navigateToCases}> | ||
{i18n.VIEW_RECENT_CASES} | ||
</EuiButton> | ||
</> | ||
)} | ||
</EuiPanel> | ||
</HoverVisibilityContainer> | ||
); | ||
}); | ||
|
||
CasesTable.displayName = 'CasesTable'; | ||
|
||
const getTableColumns: GetTableColumns = () => [ | ||
{ | ||
field: 'id', | ||
name: i18n.CASES_TABLE_COLUMN_NAME, | ||
truncateText: true, | ||
textOnly: true, | ||
'data-test-subj': 'recentlyCreatedCaseName', | ||
|
||
render: (id: string, { name }) => <CaseDetailsLink detailName={id}>{name}</CaseDetailsLink>, | ||
}, | ||
{ | ||
field: 'note', | ||
name: i18n.CASES_TABLE_COLUMN_NOTE, | ||
truncateText: true, | ||
textOnly: true, | ||
render: (note: string) => ( | ||
<EuiText data-test-subj="recentlyCreatedCaseNote" size="s"> | ||
{note} | ||
</EuiText> | ||
), | ||
}, | ||
{ | ||
field: 'createdAt', | ||
name: i18n.CASES_TABLE_COLUMN_TIME, | ||
render: (createdAt: string) => ( | ||
<FormattedDate | ||
fieldName={i18n.CASES_TABLE_COLUMN_TIME} | ||
value={createdAt} | ||
className="eui-textTruncate" | ||
dateFormat="MMMM D, YYYY" | ||
/> | ||
), | ||
'data-test-subj': 'recentlyCreatedCaseTime', | ||
}, | ||
{ | ||
field: 'createdBy', | ||
name: i18n.CASES_TABLE_COLUMN_CREATED_BY, | ||
render: (createdBy: string) => ( | ||
<EuiText data-test-subj="recentlyCreatedCaseCreatedBy" size="s"> | ||
{createdBy} | ||
</EuiText> | ||
), | ||
}, | ||
{ | ||
field: 'status', | ||
name: i18n.CASES_TABLE_COLUMN_STATUS, | ||
render: (status: CaseStatuses) => <StatusBadge status={status} />, | ||
'data-test-subj': 'recentlyCreatedCaseStatus', | ||
}, | ||
]; |
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
Oops, something went wrong.