diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table.tsx index 0ed43c32cdb60..5a541c9e56af2 100644 --- a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table.tsx +++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table.tsx @@ -237,6 +237,7 @@ export const SpikeAnalysisTable: FC = ({ ), render: (_, { pValue }) => { + if (!pValue) return NOT_AVAILABLE; const label = getFailedTransactionsCorrelationImpactLabel(pValue); return label ? {label.impact} : null; }, diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_expanded_row.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_expanded_row.tsx deleted file mode 100644 index 9eb235c07a1a1..0000000000000 --- a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_expanded_row.tsx +++ /dev/null @@ -1,355 +0,0 @@ -/* - * 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, { FC, useCallback, useMemo, useState } from 'react'; -import { sortBy } from 'lodash'; - -import { - EuiBadge, - EuiBasicTable, - EuiBasicTableColumn, - EuiIcon, - EuiTableSortingType, - EuiToolTip, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { escapeKuery } from '@kbn/es-query'; -import type { ChangePoint } from '@kbn/ml-agg-utils'; - -import { SEARCH_QUERY_LANGUAGE } from '../../application/utils/search_utils'; -import { useEuiTheme } from '../../hooks/use_eui_theme'; -import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; - -import { MiniHistogram } from '../mini_histogram'; - -import { getFailedTransactionsCorrelationImpactLabel } from './get_failed_transactions_correlation_impact_label'; - -const NARROW_COLUMN_WIDTH = '120px'; -const ACTIONS_COLUMN_WIDTH = '60px'; -const NOT_AVAILABLE = '--'; - -const PAGINATION_SIZE_OPTIONS = [5, 10, 20, 50]; -const DEFAULT_SORT_FIELD = 'pValue'; -const DEFAULT_SORT_DIRECTION = 'asc'; -const viewInDiscoverMessage = i18n.translate( - 'xpack.aiops.spikeAnalysisTable.linksMenu.viewInDiscover', - { - defaultMessage: 'View in Discover', - } -); - -interface SpikeAnalysisTableExpandedRowProps { - changePoints: ChangePoint[]; - dataViewId?: string; - loading: boolean; - onPinnedChangePoint?: (changePoint: ChangePoint | null) => void; - onSelectedChangePoint?: (changePoint: ChangePoint | null) => void; - selectedChangePoint?: ChangePoint; -} - -export const SpikeAnalysisTableExpandedRow: FC = ({ - changePoints, - dataViewId, - loading, - onPinnedChangePoint, - onSelectedChangePoint, - selectedChangePoint, -}) => { - const euiTheme = useEuiTheme(); - - const [pageIndex, setPageIndex] = useState(0); - const [pageSize, setPageSize] = useState(10); - const [sortField, setSortField] = useState(DEFAULT_SORT_FIELD); - const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>(DEFAULT_SORT_DIRECTION); - - const { application, share, data } = useAiopsAppContext(); - - const discoverLocator = useMemo( - () => share.url.locators.get('DISCOVER_APP_LOCATOR'), - [share.url.locators] - ); - - const discoverUrlError = useMemo(() => { - if (!application.capabilities.discover?.show) { - const discoverNotEnabled = i18n.translate( - 'xpack.aiops.spikeAnalysisTable.discoverNotEnabledErrorMessage', - { - defaultMessage: 'Discover is not enabled', - } - ); - - return discoverNotEnabled; - } - if (!discoverLocator) { - const discoverLocatorMissing = i18n.translate( - 'xpack.aiops.spikeAnalysisTable.discoverLocatorMissingErrorMessage', - { - defaultMessage: 'No locator for Discover detected', - } - ); - - return discoverLocatorMissing; - } - if (!dataViewId) { - const autoGeneratedDiscoverLinkError = i18n.translate( - 'xpack.aiops.spikeAnalysisTable.autoGeneratedDiscoverLinkErrorMessage', - { - defaultMessage: 'Unable to link to Discover; no data view exists for this index', - } - ); - - return autoGeneratedDiscoverLinkError; - } - }, [application.capabilities.discover?.show, dataViewId, discoverLocator]); - - const generateDiscoverUrl = async (changePoint: ChangePoint) => { - if (discoverLocator !== undefined) { - const url = await discoverLocator.getRedirectUrl({ - indexPatternId: dataViewId, - timeRange: data.query.timefilter.timefilter.getTime(), - filters: data.query.filterManager.getFilters(), - query: { - language: SEARCH_QUERY_LANGUAGE.KUERY, - query: `${escapeKuery(changePoint.fieldName)}:${escapeKuery( - String(changePoint.fieldValue) - )}`, - }, - }); - - return url; - } - }; - - const columns: Array> = [ - { - 'data-test-subj': 'aiopsSpikeAnalysisTableColumnFieldName', - field: 'fieldName', - name: i18n.translate('xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.fieldNameLabel', { - defaultMessage: 'Field name', - }), - sortable: true, - }, - { - 'data-test-subj': 'aiopsSpikeAnalysisTableColumnFieldValue', - field: 'fieldValue', - name: i18n.translate('xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.fieldValueLabel', { - defaultMessage: 'Field value', - }), - render: (_, { fieldValue }) => String(fieldValue).slice(0, 50), - sortable: true, - }, - { - 'data-test-subj': 'aiopsSpikeAnalysisTableColumnLogRate', - width: NARROW_COLUMN_WIDTH, - field: 'pValue', - name: ( - - <> - - - - - ), - render: (_, { histogram, fieldName, fieldValue }) => { - if (!histogram) return NOT_AVAILABLE; - return ( - - ); - }, - sortable: false, - }, - { - 'data-test-subj': 'aiopsSpikeAnalysisTableColumnPValue', - width: NARROW_COLUMN_WIDTH, - field: 'pValue', - name: ( - - <> - - - - - ), - render: (pValue: number | null) => pValue?.toPrecision(3) ?? NOT_AVAILABLE, - sortable: true, - }, - { - 'data-test-subj': 'aiopsSpikeAnalysisTableColumnImpact', - width: NARROW_COLUMN_WIDTH, - field: 'pValue', - name: ( - - <> - - - - - ), - render: (_, { pValue }) => { - if (!pValue) return NOT_AVAILABLE; - const label = getFailedTransactionsCorrelationImpactLabel(pValue); - return label ? {label.impact} : null; - }, - sortable: true, - }, - { - 'data-test-subj': 'aiOpsSpikeAnalysisTableColumnAction', - name: i18n.translate('xpack.aiops.spikeAnalysisTable.actionsColumnName', { - defaultMessage: 'Actions', - }), - actions: [ - { - name: () => ( - - - - ), - description: viewInDiscoverMessage, - type: 'button', - onClick: async (changePoint) => { - const openInDiscoverUrl = await generateDiscoverUrl(changePoint); - if (typeof openInDiscoverUrl === 'string') { - await application.navigateToUrl(openInDiscoverUrl); - } - }, - enabled: () => discoverUrlError === undefined, - }, - ], - width: ACTIONS_COLUMN_WIDTH, - }, - ]; - - const onChange = useCallback((tableSettings) => { - const { index, size } = tableSettings.page; - const { field, direction } = tableSettings.sort; - - setPageIndex(index); - setPageSize(size); - setSortField(field); - setSortDirection(direction); - }, []); - - const { pagination, pageOfItems, sorting } = useMemo(() => { - const pageStart = pageIndex * pageSize; - const itemCount = changePoints?.length ?? 0; - - let items: ChangePoint[] = changePoints ?? []; - items = sortBy(changePoints, (item) => { - if (item && typeof item[sortField] === 'string') { - // @ts-ignore Object is possibly null or undefined - return item[sortField].toLowerCase(); - } - return item[sortField]; - }); - items = sortDirection === 'asc' ? items : items.reverse(); - - return { - pageOfItems: items.slice(pageStart, pageStart + pageSize), - pagination: { - pageIndex, - pageSize, - totalItemCount: itemCount, - pageSizeOptions: PAGINATION_SIZE_OPTIONS, - }, - sorting: { - sort: { - field: sortField, - direction: sortDirection, - }, - }, - }; - }, [pageIndex, pageSize, sortField, sortDirection, changePoints]); - - // Don't pass on the `loading` state to the table itself because - // it disables hovering events. Because the mini histograms take a while - // to load, hovering would not update the main chart. Instead, - // the loading state is shown by the progress bar on the outer component level. - // The outer component also will display a prompt when no data was returned - // running the analysis and will hide this table. - - return ( - } - rowProps={(changePoint) => { - return { - 'data-test-subj': `aiopsSpikeAnalysisTableRow row-${changePoint.fieldName}-${changePoint.fieldValue}`, - onClick: () => { - if (onPinnedChangePoint) { - onPinnedChangePoint(changePoint); - } - }, - onMouseEnter: () => { - if (onSelectedChangePoint) { - onSelectedChangePoint(changePoint); - } - }, - onMouseLeave: () => { - if (onSelectedChangePoint) { - onSelectedChangePoint(null); - } - }, - style: - selectedChangePoint && - selectedChangePoint.fieldValue === changePoint.fieldValue && - selectedChangePoint.fieldName === changePoint.fieldName - ? { - backgroundColor: euiTheme.euiColorLightestShade, - } - : null, - }; - }} - /> - ); -}; diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx index 7a6f5508aea82..a046250db20b2 100644 --- a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx +++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx @@ -25,7 +25,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { ChangePoint } from '@kbn/ml-agg-utils'; import { useEuiTheme } from '../../hooks/use_eui_theme'; -import { SpikeAnalysisTableExpandedRow } from './spike_analysis_table_expanded_row'; +import { SpikeAnalysisTable } from './spike_analysis_table'; const NARROW_COLUMN_WIDTH = '120px'; const EXPAND_COLUMN_WIDTH = '40px'; @@ -100,7 +100,7 @@ export const SpikeAnalysisGroupsTable: FC = ({ } itemIdToExpandedRowMapValues[item.id] = ( - = ({ toggleDetails(item)} - aria-label={itemIdToExpandedRowMap[item.id] ? 'Collapse' : 'Expand'} + aria-label={ + itemIdToExpandedRowMap[item.id] + ? i18n.translate( + 'xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.collapseAriaLabel', + { + defaultMessage: 'Collapse', + } + ) + : i18n.translate( + 'xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.expandAriaLabel', + { + defaultMessage: 'Expand', + } + ) + } iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'} /> ), diff --git a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx index 7797a9a55ef8c..35dce429d54aa 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx @@ -11,9 +11,10 @@ import { type EuiBasicTableProps, type Pagination, type CriteriaWithPagination, + EuiLink, } from '@elastic/eui'; import React from 'react'; -import { Link, useHistory, generatePath } from 'react-router-dom'; +import { generatePath } from 'react-router-dom'; import { pagePathGetters } from '@kbn/fleet-plugin/public'; import { i18n } from '@kbn/i18n'; import { TimestampTableCell } from '../../components/timestamp_table_cell'; @@ -31,20 +32,34 @@ interface BenchmarksTableProps } const AgentPolicyButtonLink = ({ name, id: policyId }: { name: string; id: string }) => { - const { http, application } = useKibana().services; + const { http } = useKibana().services; const [fleetBase, path] = pagePathGetters.policy_details({ policyId }); + + return {name}; +}; + +const IntegrationButtonLink = ({ + packageName, + policyId, + packagePolicyId, +}: { + packageName: string; + packagePolicyId: string; + policyId: string; +}) => { + const { application } = useKibana().services; + return ( - { - e.stopPropagation(); - e.preventDefault(); - application.navigateToApp('fleet', { path }); - }} + - {name} - + {packageName} + ); }; @@ -55,18 +70,11 @@ const BENCHMARKS_TABLE_COLUMNS: Array> = [ defaultMessage: 'Integration', }), render: (packageName, benchmark) => ( - { - e.stopPropagation(); - }} - > - {packageName} - + ), truncateText: true, sortable: true, @@ -146,18 +154,6 @@ export const BenchmarksTable = ({ sorting, ...rest }: BenchmarksTableProps) => { - const history = useHistory(); - - const getRowProps: EuiBasicTableProps['rowProps'] = (benchmark) => ({ - onClick: () => - history.push( - generatePath(cloudPosturePages.rules.path, { - packagePolicyId: benchmark.package_policy.id, - policyId: benchmark.package_policy.policy_id, - }) - ), - }); - const pagination: Pagination = { pageIndex: Math.max(pageIndex - 1, 0), pageSize, @@ -173,7 +169,6 @@ export const BenchmarksTable = ({ data-test-subj={rest['data-test-subj']} items={benchmarks} columns={BENCHMARKS_TABLE_COLUMNS} - rowProps={getRowProps} itemId={(item) => [item.agent_policy.id, item.package_policy.id].join('/')} pagination={pagination} onChange={onChange} diff --git a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx index c67ea0df8a180..8912b38f57a20 100644 --- a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx @@ -148,7 +148,11 @@ export const PageTemplate: React.FC = ({ })} )} - {renderContent()} + + + {renderContent()} + + diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_empty_state.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_empty_state.tsx index 3cc75f33b0f7d..a5a6f15f2de9d 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_empty_state.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_empty_state.tsx @@ -6,49 +6,53 @@ */ import React, { memo } from 'react'; -import styled, { css } from 'styled-components'; +import styled from 'styled-components'; import { EuiLink, EuiEmptyPrompt } from '@elastic/eui'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { i18n } from '@kbn/i18n'; import { ManagementEmptyStateWrapper } from '../../management_empty_state_wrapper'; const EmptyPrompt = styled(EuiEmptyPrompt)` - ${() => css` - max-width: 100%; - `} + max-width: 100%; `; -export const ActionsLogEmptyState = memo(() => { - const { docLinks } = useKibana().services; +export const ActionsLogEmptyState = memo( + ({ 'data-test-subj': dataTestSubj }: { 'data-test-subj'?: string }) => { + const { docLinks } = useKibana().services; - return ( - - - {i18n.translate('xpack.securitySolution.actions_log.empty.title', { - defaultMessage: 'Actions history is empty', - })} - - } - body={ -
- {i18n.translate('xpack.securitySolution.actions_log.empty.content', { - defaultMessage: 'No response actions performed', - })} -
- } - actions={[ - - {i18n.translate('xpack.securitySolution.actions_log.empty.link', { - defaultMessage: 'Read more about response actions', - })} - , - ]} - /> -
- ); -}); + return ( + + + {i18n.translate('xpack.securitySolution.actions_log.empty.title', { + defaultMessage: 'Actions history is empty', + })} + + } + body={ +
+ {i18n.translate('xpack.securitySolution.actions_log.empty.content', { + defaultMessage: 'No response actions performed', + })} +
+ } + actions={[ + + {i18n.translate('xpack.securitySolution.actions_log.empty.link', { + defaultMessage: 'Read more about response actions', + })} + , + ]} + /> +
+ ); + } +); ActionsLogEmptyState.displayName = 'ActionsLogEmptyState'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx index e76fb65e582ce..549b03fedfbf1 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import * as reactTestingLibrary from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; +import type { IHttpFetchError } from '@kbn/core-http-browser'; import { createAppRootMockRenderer, type AppContextTestRender, @@ -23,7 +24,7 @@ import uuid from 'uuid'; let mockUseGetEndpointActionList: { isFetched?: boolean; isFetching?: boolean; - error?: null; + error?: Partial | null; data?: ActionListApiResponse; refetch: () => unknown; }; @@ -166,6 +167,28 @@ describe('Response Actions Log', () => { jest.clearAllMocks(); }); + describe('When index does not exist yet', () => { + it('should show global loader when waiting for response', () => { + mockUseGetEndpointActionList = { + ...baseMockedActionList, + isFetched: false, + isFetching: true, + }; + render(); + expect(renderResult.getByTestId(`${testPrefix}-global-loader`)).toBeTruthy(); + }); + it('should show empty page when there is no index', () => { + mockUseGetEndpointActionList = { + ...baseMockedActionList, + error: { + body: { statusCode: 404, message: 'index_not_found_exception' }, + }, + }; + render(); + expect(renderResult.getByTestId(`${testPrefix}-empty-state`)).toBeTruthy(); + }); + }); + describe('Without data', () => { it('should show date filters', () => { render(); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx index fd3b431352bef..d7b923dec5058 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx @@ -116,9 +116,9 @@ export const ResponseActionsLog = memo< Pick & { showHostNames?: boolean; isFlyout?: boolean; - setIsDataCallback?: (isData: boolean) => void; + setIsDataInResponse?: (isData: boolean) => void; } ->(({ agentIds, showHostNames = false, isFlyout = true, setIsDataCallback }) => { +>(({ agentIds, showHostNames = false, isFlyout = true, setIsDataInResponse }) => { const { pagination: paginationFromUrlParams, setPagination: setPaginationOnUrlParams } = useUrlPagination(); const { @@ -135,6 +135,7 @@ export const ResponseActionsLog = memo< [k: ActionListApiResponse['data'][number]['id']]: React.ReactNode; }>({}); + // Used to decide if display global loader or not (only the fist time tha page loads) const [isFirstAttempt, setIsFirstAttempt] = useState(true); const [queryParams, setQueryParams] = useState({ @@ -182,6 +183,26 @@ export const ResponseActionsLog = memo< { retry: false } ); + // Hide page header when there is no actions index calling the setIsDataInResponse with false value. + // Otherwise, it shows the page header calling the setIsDataInResponse with true value and it also keeps track + // if the API request was done for the first time. + useEffect(() => { + if ( + !isFetching && + error?.body?.statusCode === 404 && + error?.body?.message === 'index_not_found_exception' + ) { + if (setIsDataInResponse) { + setIsDataInResponse(false); + } + } else if (!isFetching && actionList) { + setIsFirstAttempt(false); + if (setIsDataInResponse) { + setIsDataInResponse(true); + } + } + }, [actionList, error, isFetching, setIsDataInResponse]); + // handle auto refresh data const onRefresh = useCallback(() => { if (dateRangePickerState.autoRefreshOptions.enabled) { @@ -597,27 +618,10 @@ export const ResponseActionsLog = memo< [getTestId, pagedResultsCount.fromCount, pagedResultsCount.toCount, totalItemCount] ); - useEffect(() => { - if ( - !isFetching && - error?.body?.statusCode === 404 && - error?.body?.message === 'index_not_found_exception' - ) { - if (setIsDataCallback) { - setIsDataCallback(false); - } - } else if (!isFetching && actionList) { - setIsFirstAttempt(false); - if (setIsDataCallback) { - setIsDataCallback(true); - } - } - }, [actionList, error, isFetching, setIsDataCallback]); - if (error?.body?.statusCode === 404 && error?.body?.message === 'index_not_found_exception') { - return ; + return ; } else if (isFetching && isFirstAttempt) { - return ; + return ; } return ( <> diff --git a/x-pack/plugins/security_solution/public/management/components/management_empty_state_wrapper.tsx b/x-pack/plugins/security_solution/public/management/components/management_empty_state_wrapper.tsx index 19b0cf44736f0..db25aea95f917 100644 --- a/x-pack/plugins/security_solution/public/management/components/management_empty_state_wrapper.tsx +++ b/x-pack/plugins/security_solution/public/management/components/management_empty_state_wrapper.tsx @@ -13,12 +13,20 @@ export const StyledEuiFlexGroup = styled(EuiFlexGroup)` min-height: calc(100vh - 140px); `; -export const ManagementEmptyStateWrapper = memo(({ children }) => { - return ( - - {children} - - ); -}); +export const ManagementEmptyStateWrapper = memo( + ({ + children, + 'data-test-subj': dataTestSubj, + }: { + children: React.ReactNode; + 'data-test-subj'?: string; + }) => { + return ( + + {children} + + ); + } +); ManagementEmptyStateWrapper.displayName = 'ManagementEmptyStateWrapper'; diff --git a/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx index 93de9a0cd0c8d..734db973826c9 100644 --- a/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import * as reactTestingLibrary from '@testing-library/react'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; import userEvent from '@testing-library/user-event'; +import type { IHttpFetchError } from '@kbn/core-http-browser'; import { type AppContextTestRender, createAppRootMockRenderer, @@ -22,7 +23,7 @@ import { useGetEndpointsList } from '../../../hooks/endpoint/use_get_endpoints_l let mockUseGetEndpointActionList: { isFetched?: boolean; isFetching?: boolean; - error?: null; + error?: Partial | null; data?: ActionListApiResponse; refetch: () => unknown; }; @@ -160,6 +161,32 @@ describe('Action history page', () => { jest.clearAllMocks(); }); + describe('Hide/Show header', () => { + it('should show header when data is in', () => { + reactTestingLibrary.act(() => { + history.push('/administration/action_history?page=3&pageSize=20'); + }); + render(); + const { getByTestId } = renderResult; + expect(getByTestId('responseActionsPage-header')).toBeTruthy(); + }); + + it('should not show header when there is no actions index', () => { + reactTestingLibrary.act(() => { + history.push('/administration/action_history?page=3&pageSize=20'); + }); + mockUseGetEndpointActionList = { + ...baseMockedActionList, + error: { + body: { statusCode: 404, message: 'index_not_found_exception' }, + }, + }; + render(); + const { queryByTestId } = renderResult; + expect(queryByTestId('responseActionsPage-header')).toBeNull(); + }); + }); + describe('Read from URL params', () => { it('should read and set paging values from URL params', () => { reactTestingLibrary.act(() => { diff --git a/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.tsx b/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.tsx index 595f5cee928c2..e1f3705b4489c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.tsx @@ -13,7 +13,7 @@ import { UX_MESSAGES } from '../../../components/endpoint_response_actions_list/ export const ResponseActionsListPage = () => { const [hideHeader, setHideHeader] = useState(true); - const setIsDataCallback = useCallback((isData: boolean) => { + const resetPageHeader = useCallback((isData: boolean) => { setHideHeader(!isData); }, []); return ( @@ -26,7 +26,7 @@ export const ResponseActionsListPage = () => { ); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/action_list.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/action_list.ts index aa5e45d3d66e6..eb53a6a4338a8 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/action_list.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/action_list.ts @@ -205,9 +205,10 @@ const getActionDetailsList = async ({ throw err; } - if (!actionRequests?.body?.hits?.hits) + if (!actionRequests?.body?.hits?.hits) { // return empty details array return { actionDetails: [], totalRecords: 0 }; + } // format endpoint actions into { type, item } structure const formattedActionRequests = formatEndpointActionResults(actionRequests?.body?.hits?.hits); diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts index ccd91253012b3..f9f1252f4765e 100644 --- a/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts +++ b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts @@ -25,6 +25,9 @@ import { ENDING_BREADCRUMB, FIELD_BROWSER, FIELD_BROWSER_MODAL, + FIELD_SELECTOR_TOGGLE_BUTTON, + FIELD_SELECTOR_INPUT, + FIELD_SELECTOR_LIST, } from '../screens/indicators'; import { login } from '../tasks/login'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; @@ -143,16 +146,13 @@ describe('Indicators', () => { it('should have the default selected field, then update when user selects', () => { const threatFeedName = 'threat.feed.name'; - cy.get(`${FIELD_SELECTOR}`).should('have.value', threatFeedName); + cy.get(`${FIELD_SELECTOR_INPUT}`).eq(0).should('have.text', threatFeedName); const threatIndicatorIp: string = 'threat.indicator.ip'; - cy.get(`${FIELD_SELECTOR}`) - .should('exist') - .select(threatIndicatorIp) - .should('have.value', threatIndicatorIp); + cy.get(`${FIELD_SELECTOR_TOGGLE_BUTTON}`).should('exist').click(); - cy.get(`${FIELD_SELECTOR}`).should('have.value', threatIndicatorIp); + cy.get(`${FIELD_SELECTOR_LIST}`).should('exist').contains(threatIndicatorIp); }); }); diff --git a/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts b/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts index 4ceaa0c020e99..b5582da6ce8ef 100644 --- a/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts +++ b/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts @@ -41,6 +41,12 @@ export const INDICATOR_TYPE_CELL = '[data-gridcell-column-id="threat.indicator.t export const FIELD_SELECTOR = '[data-test-subj="tiIndicatorFieldSelectorDropdown"]'; +export const FIELD_SELECTOR_INPUT = '[data-test-subj="comboBoxInput"]'; + +export const FIELD_SELECTOR_TOGGLE_BUTTON = '[data-test-subj="comboBoxToggleListButton"]'; + +export const FIELD_SELECTOR_LIST = '[data-test-subj="comboBoxOptionsList"]'; + export const FIELD_BROWSER = `[data-test-subj="show-field-browser"]`; export const FIELD_BROWSER_MODAL = `[data-test-subj="fields-browser-container"]`; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/__snapshots__/indicators_barchart_wrapper.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/__snapshots__/indicators_barchart_wrapper.test.tsx.snap index de4d6c198961f..bc2b71303138b 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/__snapshots__/indicators_barchart_wrapper.test.tsx.snap +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/__snapshots__/indicators_barchart_wrapper.test.tsx.snap @@ -21,43 +21,67 @@ Object { class="euiFlexItem euiFlexItem--flexGrowZero" >
-
- + Stack by +
- + class="euiComboBoxPill euiComboBoxPill--plainText" + > + threat.feed.name + +
+ +
+
+
+
+ +
@@ -102,43 +126,67 @@ Object { class="euiFlexItem euiFlexItem--flexGrowZero" >
-
- + Stack by +
- + class="euiComboBoxPill euiComboBoxPill--plainText" + > + threat.feed.name + +
+ +
+
+
+
+ +
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_selector/__snapshots__/indicators_field_selector.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_selector/__snapshots__/indicators_field_selector.test.tsx.snap index 735a1c34718c0..9346a739b9dae 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_selector/__snapshots__/indicators_field_selector.test.tsx.snap +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_selector/__snapshots__/indicators_field_selector.test.tsx.snap @@ -6,43 +6,67 @@ Object { "baseElement":
-
- + Stack by +
- + class="euiComboBoxPill euiComboBoxPill--plainText" + > + threat.feed.name + +
+ +
+
+
+
+ +
@@ -50,43 +74,67 @@ Object { , "container":
-
- + Stack by +
- + class="euiComboBoxPill euiComboBoxPill--plainText" + > + threat.feed.name + +
+ +
+
+
+
+ +
@@ -151,32 +199,67 @@ Object { "baseElement":
-
- +
+
+
+
+ +
@@ -184,32 +267,67 @@ Object { , "container":
-
- +
+
+
+
+ +
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_selector/indicators_field_selector.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_selector/indicators_field_selector.test.tsx index e3bd4129d4fc5..0d62bbcef6219 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_selector/indicators_field_selector.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_selector/indicators_field_selector.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { DataView, DataViewField } from '@kbn/data-views-plugin/common'; import { TestProvidersComponent } from '../../../../common/mocks/test_providers'; -import { DROPDOWN_TEST_ID, IndicatorsFieldSelector } from './indicators_field_selector'; +import { IndicatorsFieldSelector } from './indicators_field_selector'; const mockIndexPattern: DataView = { fields: [ @@ -51,11 +51,5 @@ describe('', () => { ); expect(component).toMatchSnapshot(); - - const dropdownOptions: string = component.getByTestId(DROPDOWN_TEST_ID).innerHTML; - const optionsCount: number = (dropdownOptions.match(/