Standardize page header layouts in Security (#115393)

* Standardize page header layouts in Security

* Update Observability create case layout
This commit is contained in:
Pablo Machado 2021-10-21 13:20:11 +02:00 committed by GitHub
parent 941a6c9bb9
commit c274d39152
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 216 additions and 298 deletions

View file

@ -28,7 +28,7 @@ import { useGetActionLicense } from '../../containers/use_get_action_license';
import { useGetCases } from '../../containers/use_get_cases';
import { usePostComment } from '../../containers/use_post_comment';
import { CaseDetailsHrefSchema, CasesNavigation } from '../links';
import { Panel } from '../panel';
import { getActionLicenseError } from '../use_push_to_service/helpers';
import { useCasesColumns } from './columns';
import { getExpandedRowMap } from './expanded_row';
@ -240,8 +240,6 @@ export const AllCasesGeneric = React.memo<AllCasesGenericProps>(
const isCasesLoading = useMemo(() => loading.indexOf('cases') > -1, [loading]);
const isDataEmpty = useMemo(() => data.total === 0, [data]);
const TableWrap = useMemo(() => (isSelectorView ? 'span' : Panel), [isSelectorView]);
const tableRowProps = useCallback(
(theCase: Case) => {
const onTableRowClick = memoize(async () => {
@ -289,48 +287,43 @@ export const AllCasesGeneric = React.memo<AllCasesGenericProps>(
className="essentialAnimation"
$isShow={(isCasesLoading || isLoading || isCommentUpdating) && !isDataEmpty}
/>
<TableWrap
data-test-subj="table-wrap"
loading={!isSelectorView ? isCasesLoading : undefined}
>
<CasesTableFilters
countClosedCases={data.countClosedCases}
countOpenCases={data.countOpenCases}
countInProgressCases={data.countInProgressCases}
onFilterChanged={onFilterChangedCallback}
initial={{
search: filterOptions.search,
reporters: filterOptions.reporters,
tags: filterOptions.tags,
status: filterOptions.status,
}}
setFilterRefetch={setFilterRefetch}
hiddenStatuses={hiddenStatuses}
/>
<CasesTable
columns={columns}
createCaseNavigation={createCaseNavigation}
data={data}
filterOptions={filterOptions}
goToCreateCase={goToCreateCase}
handleIsLoading={handleIsLoading}
isCasesLoading={isCasesLoading}
isCommentUpdating={isCommentUpdating}
isDataEmpty={isDataEmpty}
isSelectorView={isSelectorView}
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
onChange={tableOnChangeCallback}
pagination={pagination}
refreshCases={refreshCases}
selectedCases={selectedCases}
selection={euiBasicTableSelectionProps}
showActions={showActions}
sorting={sorting}
tableRef={tableRef}
tableRowProps={tableRowProps}
userCanCrud={userCanCrud}
/>
</TableWrap>
<CasesTableFilters
countClosedCases={data.countClosedCases}
countOpenCases={data.countOpenCases}
countInProgressCases={data.countInProgressCases}
onFilterChanged={onFilterChangedCallback}
initial={{
search: filterOptions.search,
reporters: filterOptions.reporters,
tags: filterOptions.tags,
status: filterOptions.status,
}}
setFilterRefetch={setFilterRefetch}
hiddenStatuses={hiddenStatuses}
/>
<CasesTable
columns={columns}
createCaseNavigation={createCaseNavigation}
data={data}
filterOptions={filterOptions}
goToCreateCase={goToCreateCase}
handleIsLoading={handleIsLoading}
isCasesLoading={isCasesLoading}
isCommentUpdating={isCommentUpdating}
isDataEmpty={isDataEmpty}
isSelectorView={isSelectorView}
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
onChange={tableOnChangeCallback}
pagination={pagination}
refreshCases={refreshCases}
selectedCases={selectedCases}
selection={euiBasicTableSelectionProps}
showActions={showActions}
sorting={sorting}
tableRef={tableRef}
tableRowProps={tableRowProps}
userCanCrud={userCanCrud}
/>
</>
);
}

View file

@ -49,7 +49,7 @@ export const CasesTableHeader: FunctionComponent<Props> = ({
showTitle = true,
userCanCrud,
}) => (
<HeaderPage title={showTitle ? i18n.PAGE_TITLE : ''}>
<HeaderPage title={showTitle ? i18n.PAGE_TITLE : ''} border>
<EuiFlexGroup alignItems="center" gutterSize="m" wrap={true} data-test-subj="all-cases-header">
{userCanCrud ? (
<>

View file

@ -1,17 +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 { mount } from 'enzyme';
import { Panel } from '.';
import React from 'react';
describe('Panel', () => {
test('it does not have the boolean loading as a Eui Property', () => {
const wrapper = mount(<Panel loading={true} />);
expect(Object.keys(wrapper.find('EuiPanel').props())).not.toContain('loading');
});
});

View file

@ -1,37 +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 styled from 'styled-components';
import React from 'react';
import { EuiPanel } from '@elastic/eui';
/**
* The reason for the type of syntax below of:
* `styled(({ loading, ...props })`
* is filter out the "loading" attribute from being put on the DOM
* and getting one of the stack traces from
* ```
* ReactJS about non-standard HTML such as this one:
* Warning: Received `true` for a non-boolean attribute `loading`.
* If you want to write it to the DOM, pass a string instead: loading="true" or loading={value.toString()}.
* ```
*
* Ref: https://github.com/styled-components/styled-components/issues/1198#issuecomment-425650423
* Ref: https://github.com/elastic/kibana/pull/41596#issuecomment-514418978
* Ref: https://www.styled-components.com/docs/faqs#why-am-i-getting-html-attribute-warnings
* Ref: https://reactjs.org/blog/2017/09/08/dom-attributes-in-react-16.html
*/
export const Panel = styled(({ loading, ...props }) => <EuiPanel {...props} hasBorder />)`
position: relative;
${({ loading }) =>
loading &&
`
overflow: hidden;
`}
`;
Panel.displayName = 'Panel';

View file

@ -6,7 +6,6 @@
*/
import React, { useCallback } from 'react';
import { EuiPanel } from '@elastic/eui';
import { useKibana } from '../../../../utils/kibana_react';
import { getCaseDetailsUrl } from '../../../../pages/cases/links';
@ -29,16 +28,12 @@ export const Create = React.memo(() => {
[casesUrl, navigateToUrl]
);
return (
<EuiPanel>
{cases.getCreateCase({
disableAlerts: true,
onCancel: handleSetIsCancel,
onSuccess,
owner: [CASES_OWNER],
})}
</EuiPanel>
);
return cases.getCreateCase({
disableAlerts: true,
onCancel: handleSetIsCancel,
onSuccess,
owner: [CASES_OWNER],
});
});
Create.displayName = 'Create';

View file

@ -6,7 +6,6 @@
*/
import React, { useCallback } from 'react';
import { EuiPanel } from '@elastic/eui';
import { getCaseDetailsUrl, getCaseUrl } from '../../../common/components/link_to';
import { useKibana } from '../../../common/lib/kibana';
@ -40,25 +39,21 @@ export const Create = React.memo(() => {
[navigateToApp, search]
);
return (
<EuiPanel hasBorder>
{cases.getCreateCase({
onCancel: handleSetIsCancel,
onSuccess,
timelineIntegration: {
editor_plugins: {
parsingPlugin: timelineMarkdownPlugin.parser,
processingPluginRenderer: timelineMarkdownPlugin.renderer,
uiPlugin: timelineMarkdownPlugin.plugin,
},
hooks: {
useInsertTimeline,
},
},
owner: [APP_ID],
})}
</EuiPanel>
);
return cases.getCreateCase({
onCancel: handleSetIsCancel,
onSuccess,
timelineIntegration: {
editor_plugins: {
parsingPlugin: timelineMarkdownPlugin.parser,
processingPluginRenderer: timelineMarkdownPlugin.renderer,
uiPlugin: timelineMarkdownPlugin.plugin,
},
hooks: {
useInsertTimeline,
},
},
owner: [APP_ID],
});
});
Create.displayName = 'Create';

View file

@ -47,7 +47,7 @@ export const CreateCasePage = React.memo(() => {
return (
<>
<SecuritySolutionPageWrapper>
<CaseHeaderPage backOptions={backOptions} title={i18n.CREATE_TITLE} />
<CaseHeaderPage backOptions={backOptions} title={i18n.CREATE_TITLE} border />
<Create />
</SecuritySolutionPageWrapper>
<SpyRoute pageName={SecurityPageName.case} />

View file

@ -23,7 +23,7 @@ import { AutoDownload } from '../../../../../../common/components/auto_download/
import { useKibana } from '../../../../../../common/lib/kibana';
import { useFormatUrl } from '../../../../../../common/components/link_to';
import { Loader } from '../../../../../../common/components/loader';
import { Panel } from '../../../../../../common/components/panel';
import { DetectionEngineHeaderPage } from '../../../../../components/detection_engine_header_page';
import * as i18n from './translations';
@ -343,67 +343,68 @@ export const ExceptionListsTable = React.memo(() => {
<>
<DetectionEngineHeaderPage
title={i18n.ALL_EXCEPTIONS}
border
subtitle={i18n.ALL_EXCEPTIONS_DESCRIPTION}
subtitle2={timelines.getLastUpdated({ showUpdating: loading, updatedAt: lastUpdated })}
/>
<Panel loading={!initLoading && loadingTableInfo} data-test-subj="allExceptionListsPanel">
<>
{loadingTableInfo && (
<EuiProgress
data-test-subj="loadingRulesInfoProgress"
size="xs"
position="absolute"
color="accent"
<div data-test-subj="allExceptionListsPanel">
{loadingTableInfo && (
<EuiProgress
data-test-subj="loadingRulesInfoProgress"
size="xs"
position="absolute"
color="accent"
/>
)}
{!initLoading && <ExceptionsSearchBar onSearch={handleSearch} />}
<EuiSpacer size="m" />
{loadingTableInfo && !initLoading && !showReferenceErrorModal && (
<Loader data-test-subj="loadingPanelAllRulesTable" overlay size="xl" />
)}
{initLoading ? (
<EuiLoadingContent data-test-subj="initialLoadingPanelAllRulesTable" lines={10} />
) : (
<>
<AllRulesUtilityBar
showBulkActions={false}
canBulkEdit={hasPermissions}
paginationTotal={exceptionListsWithRuleRefs.length ?? 0}
numberSelectedItems={0}
onRefresh={handleRefresh}
/>
)}
{!initLoading && <ExceptionsSearchBar onSearch={handleSearch} />}
<EuiSpacer size="m" />
<EuiBasicTable<ExceptionsTableItem>
data-test-subj="exceptions-table"
columns={exceptionsColumns}
isSelectable={hasPermissions}
itemId="id"
items={tableItems}
noItemsMessage={emptyPrompt}
onChange={handlePaginationChange}
pagination={paginationMemo}
/>
</>
)}
{loadingTableInfo && !initLoading && !showReferenceErrorModal && (
<Loader data-test-subj="loadingPanelAllRulesTable" overlay size="xl" />
)}
{initLoading ? (
<EuiLoadingContent data-test-subj="initialLoadingPanelAllRulesTable" lines={10} />
) : (
<>
<AllRulesUtilityBar
showBulkActions={false}
canBulkEdit={hasPermissions}
paginationTotal={exceptionListsWithRuleRefs.length ?? 0}
numberSelectedItems={0}
onRefresh={handleRefresh}
/>
<EuiBasicTable<ExceptionsTableItem>
data-test-subj="exceptions-table"
columns={exceptionsColumns}
isSelectable={hasPermissions}
itemId="id"
items={tableItems}
noItemsMessage={emptyPrompt}
onChange={handlePaginationChange}
pagination={paginationMemo}
/>
</>
)}
</>
</Panel>
<AutoDownload
blob={exportDownload.blob}
name={`${exportDownload.name}.ndjson`}
onDownload={handleOnDownload}
/>
<ReferenceErrorModal
cancelText={i18n.REFERENCE_MODAL_CANCEL_BUTTON}
confirmText={i18n.REFERENCE_MODAL_CONFIRM_BUTTON}
contentText={referenceModalState.contentText}
onCancel={handleCloseReferenceErrorModal}
onClose={handleCloseReferenceErrorModal}
onConfirm={handleReferenceDelete}
references={referenceModalState.rulesReferences}
showModal={showReferenceErrorModal}
titleText={i18n.REFERENCE_MODAL_TITLE}
/>
<AutoDownload
blob={exportDownload.blob}
name={`${exportDownload.name}.ndjson`}
onDownload={handleOnDownload}
/>
<ReferenceErrorModal
cancelText={i18n.REFERENCE_MODAL_CANCEL_BUTTON}
confirmText={i18n.REFERENCE_MODAL_CONFIRM_BUTTON}
contentText={referenceModalState.contentText}
onCancel={handleCloseReferenceErrorModal}
onClose={handleCloseReferenceErrorModal}
onConfirm={handleReferenceDelete}
references={referenceModalState.rulesReferences}
showModal={showReferenceErrorModal}
titleText={i18n.REFERENCE_MODAL_TITLE}
/>
</div>
</>
);
});

View file

@ -32,7 +32,6 @@ import { HeaderSection } from '../../../../../common/components/header_section';
import { useKibana, useUiSetting$ } from '../../../../../common/lib/kibana';
import { useStateToaster } from '../../../../../common/components/toasters';
import { Loader } from '../../../../../common/components/loader';
import { Panel } from '../../../../../common/components/panel';
import { PrePackagedRulesPrompt } from '../../../../components/rules/pre_packaged_rules/load_empty_prompt';
import { AllRulesTables, SortingType } from '../../../../components/rules/all_rules_tables';
import { getPrePackagedRuleStatus } from '../helpers';
@ -456,114 +455,101 @@ export const RulesTables = React.memo<RulesTableProps>(
<EuiWindowEvent event="keydown" handler={debounceResetIdleTimer} />
<EuiWindowEvent event="scroll" handler={debounceResetIdleTimer} />
<EuiWindowEvent event="load" handler={debounceResetIdleTimer} />
<Panel
loading={loading || isLoadingRules || isLoadingRulesStatuses}
data-test-subj="allRulesPanel"
{!initLoading && (loading || isLoadingRules || isLoadingAnActionOnRule) && isRefreshing && (
<EuiProgress
data-test-subj="loadingRulesInfoProgress"
size="xs"
position="absolute"
color="accent"
/>
)}
<HeaderSection
split
growLeftSplit={false}
title={i18n.ALL_RULES}
subtitle={timelines.getLastUpdated({
showUpdating: loading || isLoadingRules || isLoadingRulesStatuses,
updatedAt: lastUpdated,
})}
>
{shouldShowRulesTable && (
<RulesTableFilters
onFilterChanged={onFilterChangedCallback}
rulesCustomInstalled={rulesCustomInstalled}
rulesInstalled={rulesInstalled}
currentFilterTags={filterOptions.tags}
/>
)}
</HeaderSection>
{!initLoading &&
(loading || isLoadingRules || isLoadingAnActionOnRule) &&
!isRefreshing && <Loader data-test-subj="loadingPanelAllRulesTable" overlay size="xl" />}
{shouldShowPrepackagedRulesPrompt && (
<PrePackagedRulesPrompt
createPrePackagedRules={handleCreatePrePackagedRules}
loading={loadingCreatePrePackagedRules}
userHasPermissions={hasPermissions}
/>
)}
{initLoading && (
<EuiLoadingContent data-test-subj="initialLoadingPanelAllRulesTable" lines={10} />
)}
{showIdleModal && (
<EuiConfirmModal
title={i18n.REFRESH_PROMPT_TITLE}
onCancel={handleIdleModalContinue}
onConfirm={handleIdleModalContinue}
confirmButtonText={i18n.REFRESH_PROMPT_CONFIRM}
defaultFocusedButton="confirm"
data-test-subj="allRulesIdleModal"
>
<p>{i18n.REFRESH_PROMPT_BODY}</p>
</EuiConfirmModal>
)}
{isDeleteConfirmationVisible && (
<EuiConfirmModal
title={i18n.DELETE_CONFIRMATION_TITLE}
onCancel={handleDeletionCancel}
onConfirm={handleDeletionConfirm}
confirmButtonText={i18n.DELETE_CONFIRMATION_CONFIRM}
cancelButtonText={i18n.DELETE_CONFIRMATION_CANCEL}
buttonColor="danger"
defaultFocusedButton="confirm"
data-test-subj="allRulesDeleteConfirmationModal"
>
<p>{i18n.DELETE_CONFIRMATION_BODY}</p>
</EuiConfirmModal>
)}
{shouldShowRulesTable && (
<>
{!initLoading &&
(loading || isLoadingRules || isLoadingAnActionOnRule) &&
isRefreshing && (
<EuiProgress
data-test-subj="loadingRulesInfoProgress"
size="xs"
position="absolute"
color="accent"
/>
)}
<HeaderSection
split
growLeftSplit={false}
title={i18n.ALL_RULES}
subtitle={timelines.getLastUpdated({
showUpdating: loading || isLoadingRules || isLoadingRulesStatuses,
updatedAt: lastUpdated,
})}
>
{shouldShowRulesTable && (
<RulesTableFilters
onFilterChanged={onFilterChangedCallback}
rulesCustomInstalled={rulesCustomInstalled}
rulesInstalled={rulesInstalled}
currentFilterTags={filterOptions.tags}
/>
)}
</HeaderSection>
{!initLoading &&
(loading || isLoadingRules || isLoadingAnActionOnRule) &&
!isRefreshing && (
<Loader data-test-subj="loadingPanelAllRulesTable" overlay size="xl" />
)}
{shouldShowPrepackagedRulesPrompt && (
<PrePackagedRulesPrompt
createPrePackagedRules={handleCreatePrePackagedRules}
loading={loadingCreatePrePackagedRules}
userHasPermissions={hasPermissions}
/>
)}
{initLoading && (
<EuiLoadingContent data-test-subj="initialLoadingPanelAllRulesTable" lines={10} />
)}
{showIdleModal && (
<EuiConfirmModal
title={i18n.REFRESH_PROMPT_TITLE}
onCancel={handleIdleModalContinue}
onConfirm={handleIdleModalContinue}
confirmButtonText={i18n.REFRESH_PROMPT_CONFIRM}
defaultFocusedButton="confirm"
data-test-subj="allRulesIdleModal"
>
<p>{i18n.REFRESH_PROMPT_BODY}</p>
</EuiConfirmModal>
)}
{isDeleteConfirmationVisible && (
<EuiConfirmModal
title={i18n.DELETE_CONFIRMATION_TITLE}
onCancel={handleDeletionCancel}
onConfirm={handleDeletionConfirm}
confirmButtonText={i18n.DELETE_CONFIRMATION_CONFIRM}
cancelButtonText={i18n.DELETE_CONFIRMATION_CANCEL}
buttonColor="danger"
defaultFocusedButton="confirm"
data-test-subj="allRulesDeleteConfirmationModal"
>
<p>{i18n.DELETE_CONFIRMATION_BODY}</p>
</EuiConfirmModal>
)}
{shouldShowRulesTable && (
<>
<AllRulesUtilityBar
canBulkEdit={hasPermissions}
hasPagination={hasPagination}
paginationTotal={pagination.total ?? 0}
numberSelectedItems={selectedItemsCount}
onGetBatchItemsPopoverContent={getBatchItemsPopoverContent}
onRefresh={handleManualRefresh}
isAutoRefreshOn={isRefreshOn}
onRefreshSwitch={handleAutoRefreshSwitch}
isAllSelected={isAllSelected}
onToggleSelectAll={toggleSelectAll}
showBulkActions
/>
<AllRulesTables
selectedTab={selectedTab}
euiBasicTableSelectionProps={euiBasicTableSelectionProps}
hasPermissions={hasPermissions}
monitoringColumns={monitoringColumns}
pagination={paginationMemo}
rules={rules}
rulesColumns={rulesColumns}
rulesStatuses={rulesStatuses}
sorting={sorting}
tableOnChangeCallback={tableOnChangeCallback}
tableRef={tableRef}
/>
</>
)}
<AllRulesUtilityBar
canBulkEdit={hasPermissions}
hasPagination={hasPagination}
paginationTotal={pagination.total ?? 0}
numberSelectedItems={selectedItemsCount}
onGetBatchItemsPopoverContent={getBatchItemsPopoverContent}
onRefresh={handleManualRefresh}
isAutoRefreshOn={isRefreshOn}
onRefreshSwitch={handleAutoRefreshSwitch}
isAllSelected={isAllSelected}
onToggleSelectAll={toggleSelectAll}
showBulkActions
/>
<AllRulesTables
selectedTab={selectedTab}
euiBasicTableSelectionProps={euiBasicTableSelectionProps}
hasPermissions={hasPermissions}
monitoringColumns={monitoringColumns}
pagination={paginationMemo}
rules={rules}
rulesColumns={rulesColumns}
rulesStatuses={rulesStatuses}
sorting={sorting}
tableOnChangeCallback={tableOnChangeCallback}
tableRef={tableRef}
/>
</>
</Panel>
)}
</>
);
}

View file

@ -179,6 +179,7 @@ const HostsComponent = () => {
/>
}
title={i18n.PAGE_TITLE}
border
/>
<HostsKpiComponent

View file

@ -170,6 +170,7 @@ const NetworkComponent = React.memo<NetworkComponentProps>(
/>
}
title={i18n.PAGE_TITLE}
border
/>
<EmbeddedMap

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { EuiPanel, EuiBasicTable } from '@elastic/eui';
import { EuiBasicTable } from '@elastic/eui';
import React, { useCallback, useMemo, useRef } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
@ -194,7 +194,7 @@ export const OpenTimeline = React.memo<OpenTimelineProps>(
title={i18n.IMPORT_TIMELINE}
/>
<EuiPanel className={OPEN_TIMELINE_CLASS_NAME} hasBorder>
<div className={OPEN_TIMELINE_CLASS_NAME}>
{!!timelineFilter && timelineFilter}
<SearchRow
data-test-subj="search-row"
@ -272,7 +272,7 @@ export const OpenTimeline = React.memo<OpenTimelineProps>(
tableRef={tableRef}
totalSearchResultsCount={totalSearchResultsCount}
/>
</EuiPanel>
</div>
</>
);
}