[Cases] Move disabling features to the cases context (#119864)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Christos Nasikas 2021-12-02 19:11:10 +02:00 committed by GitHub
parent d874c4c798
commit 319fc9fb7f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 169 additions and 155 deletions

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { ConnectorTypes } from './api';
import { CasesContextValue } from './ui/types';
export const DEFAULT_DATE_FORMAT = 'dateFormat';
export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz';
@ -104,3 +105,11 @@ export const MAX_CONCURRENT_SEARCHES = 10;
*/
export const MAX_TITLE_LENGTH = 64;
/**
* Cases features
*/
export const DEFAULT_FEATURES: CasesContextValue['features'] = Object.freeze({
alerts: { sync: true },
});

View file

@ -19,6 +19,19 @@ import {
ActionConnector,
} from '../api';
interface CasesFeatures {
alerts: { sync: boolean };
}
export interface CasesContextValue {
owner: string[];
appId: string;
appTitle: string;
userCanCrud: boolean;
basePath: string;
features: CasesFeatures;
}
export interface CasesUiConfigType {
markdownPlugins: {
lens: boolean;

View file

@ -5,11 +5,12 @@
* 2.0.
*/
import React from 'react';
import { merge } from 'lodash';
import { euiDarkVars } from '@kbn/ui-shared-deps-src/theme';
import { I18nProvider } from '@kbn/i18n-react';
import React from 'react';
import { ThemeProvider } from 'styled-components';
import { SECURITY_SOLUTION_OWNER } from '../../../common';
import { CasesContextValue, DEFAULT_FEATURES, SECURITY_SOLUTION_OWNER } from '../../../common';
import { CasesProvider } from '../../components/cases_context';
import { createKibanaContextProviderMock } from '../lib/kibana/kibana_react.mock';
import { FieldHook } from '../shared_imports';
@ -17,23 +18,37 @@ import { FieldHook } from '../shared_imports';
interface Props {
children: React.ReactNode;
userCanCrud?: boolean;
features?: CasesContextValue['features'];
}
window.scrollTo = jest.fn();
const MockKibanaContextProvider = createKibanaContextProviderMock();
/** A utility for wrapping children in the providers required to run most tests */
const TestProvidersComponent: React.FC<Props> = ({ children, userCanCrud = true }) => (
<I18nProvider>
<MockKibanaContextProvider>
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
<CasesProvider value={{ owner: [SECURITY_SOLUTION_OWNER], userCanCrud }}>
{children}
</CasesProvider>
</ThemeProvider>
</MockKibanaContextProvider>
</I18nProvider>
);
const TestProvidersComponent: React.FC<Props> = ({
children,
userCanCrud = true,
features = {},
}) => {
/**
* The empty object at the beginning avoids the mutation
* of the DEFAULT_FEATURES object
*/
const featuresOptions = merge({}, DEFAULT_FEATURES, features);
return (
<I18nProvider>
<MockKibanaContextProvider>
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
<CasesProvider
value={{ owner: [SECURITY_SOLUTION_OWNER], userCanCrud, features: featuresOptions }}
>
{children}
</CasesProvider>
</ThemeProvider>
</MockKibanaContextProvider>
</I18nProvider>
);
};
export const TestProviders = React.memo(TestProvidersComponent);

View file

@ -29,7 +29,7 @@ import { useUpdateCases } from '../../containers/use_bulk_update_case';
import { useGetActionLicense } from '../../containers/use_get_action_license';
import { useConnectors } from '../../containers/configure/use_connectors';
import { useKibana } from '../../common/lib/kibana';
import { AllCasesList, AllCasesListProps } from './all_cases_list';
import { AllCasesList } from './all_cases_list';
import { CasesColumns, GetCasesColumn, useCasesColumns } from './columns';
import { triggersActionsUiMock } from '../../../../triggers_actions_ui/public/mocks';
import { registerConnectorsToMockActionRegistry } from '../../common/mock/register_connectors';
@ -64,10 +64,6 @@ const mockKibana = () => {
};
describe('AllCasesListGeneric', () => {
const defaultAllCasesListProps: AllCasesListProps = {
disableAlerts: false,
};
const dispatchResetIsDeleted = jest.fn();
const dispatchResetIsUpdated = jest.fn();
const dispatchUpdateCaseProperty = jest.fn();
@ -161,7 +157,7 @@ describe('AllCasesListGeneric', () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} />
<AllCasesList />
</TestProviders>
);
@ -215,7 +211,7 @@ describe('AllCasesListGeneric', () => {
});
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} />
<AllCasesList />
</TestProviders>
);
const checkIt = (columnName: string, key: number) => {
@ -245,7 +241,7 @@ describe('AllCasesListGeneric', () => {
});
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} />
<AllCasesList />
</TestProviders>
);
await waitFor(() => {
@ -281,7 +277,7 @@ describe('AllCasesListGeneric', () => {
});
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} />
<AllCasesList />
</TestProviders>
);
@ -293,7 +289,7 @@ describe('AllCasesListGeneric', () => {
it('should tableHeaderSortButton AllCasesList', async () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} />
<AllCasesList />
</TestProviders>
);
wrapper.find('[data-test-subj="tableHeaderSortButton"]').first().simulate('click');
@ -310,7 +306,7 @@ describe('AllCasesListGeneric', () => {
it('Updates status when status context menu is updated', async () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} />
<AllCasesList />
</TestProviders>
);
wrapper.find(`[data-test-subj="case-view-status-dropdown"] button`).first().simulate('click');
@ -351,7 +347,7 @@ describe('AllCasesListGeneric', () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} />
<AllCasesList />
</TestProviders>
);
@ -388,7 +384,7 @@ describe('AllCasesListGeneric', () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} />
<AllCasesList />
</TestProviders>
);
@ -431,7 +427,7 @@ describe('AllCasesListGeneric', () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} />
<AllCasesList />
</TestProviders>
);
wrapper.find('[data-test-subj="case-table-bulk-actions"] button').first().simulate('click');
@ -458,7 +454,7 @@ describe('AllCasesListGeneric', () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} />
<AllCasesList />
</TestProviders>
);
wrapper.find('[data-test-subj="case-table-bulk-actions"] button').first().simulate('click');
@ -481,7 +477,7 @@ describe('AllCasesListGeneric', () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} />
<AllCasesList />
</TestProviders>
);
wrapper.find('[data-test-subj="case-table-bulk-actions"] button').first().simulate('click');
@ -500,7 +496,7 @@ describe('AllCasesListGeneric', () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} />
<AllCasesList />
</TestProviders>
);
wrapper.find('[data-test-subj="case-table-bulk-actions"] button').first().simulate('click');
@ -521,7 +517,7 @@ describe('AllCasesListGeneric', () => {
mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} />
<AllCasesList />
</TestProviders>
);
await waitFor(() => {
@ -539,7 +535,7 @@ describe('AllCasesListGeneric', () => {
mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} />
<AllCasesList />
</TestProviders>
);
await waitFor(() => {
@ -552,7 +548,7 @@ describe('AllCasesListGeneric', () => {
it('should not render table utility bar when isSelectorView=true', async () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} isSelectorView={true} />
<AllCasesList isSelectorView={true} />
</TestProviders>
);
await waitFor(() => {
@ -566,7 +562,7 @@ describe('AllCasesListGeneric', () => {
it('case table should not be selectable when isSelectorView=true', async () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} isSelectorView={true} />
<AllCasesList isSelectorView={true} />
</TestProviders>
);
await waitFor(() => {
@ -588,7 +584,7 @@ describe('AllCasesListGeneric', () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} isSelectorView={true} onRowClick={onRowClick} />
<AllCasesList isSelectorView={true} onRowClick={onRowClick} />
</TestProviders>
);
wrapper.find('[data-test-subj="cases-table-add-case"]').first().simulate('click');
@ -600,7 +596,7 @@ describe('AllCasesListGeneric', () => {
it('should call onRowClick when clicking a case with modal=true', async () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} isSelectorView={true} onRowClick={onRowClick} />
<AllCasesList isSelectorView={true} onRowClick={onRowClick} />
</TestProviders>
);
@ -657,7 +653,7 @@ describe('AllCasesListGeneric', () => {
it('should NOT call onRowClick when clicking a case with modal=true', async () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} isSelectorView={false} />
<AllCasesList isSelectorView={false} />
</TestProviders>
);
wrapper.find('[data-test-subj="cases-table-row-1"]').first().simulate('click');
@ -669,7 +665,7 @@ describe('AllCasesListGeneric', () => {
it('should change the status to closed', async () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} isSelectorView={false} />
<AllCasesList isSelectorView={false} />
</TestProviders>
);
wrapper.find('button[data-test-subj="case-status-filter"]').simulate('click');
@ -684,7 +680,7 @@ describe('AllCasesListGeneric', () => {
it('should change the status to in-progress', async () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} isSelectorView={false} />
<AllCasesList isSelectorView={false} />
</TestProviders>
);
wrapper.find('button[data-test-subj="case-status-filter"]').simulate('click');
@ -699,7 +695,7 @@ describe('AllCasesListGeneric', () => {
it('should change the status to open', async () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} isSelectorView={false} />
<AllCasesList isSelectorView={false} />
</TestProviders>
);
wrapper.find('button[data-test-subj="case-status-filter"]').simulate('click');
@ -714,7 +710,7 @@ describe('AllCasesListGeneric', () => {
it('should show the correct count on stats', async () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} isSelectorView={false} />
<AllCasesList isSelectorView={false} />
</TestProviders>
);
wrapper.find('button[data-test-subj="case-status-filter"]').simulate('click');
@ -734,7 +730,7 @@ describe('AllCasesListGeneric', () => {
it('should not render status when isSelectorView=true', async () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} isSelectorView={true} />
<AllCasesList isSelectorView={true} />
</TestProviders>
);
@ -769,7 +765,7 @@ describe('AllCasesListGeneric', () => {
const wrapper = mount(
<TestProviders>
<AllCasesList {...defaultAllCasesListProps} isSelectorView={false} doRefresh={doRefresh} />
<AllCasesList isSelectorView={false} doRefresh={doRefresh} />
</TestProviders>
);

View file

@ -53,7 +53,6 @@ const getSortField = (field: string): SortFieldCase =>
export interface AllCasesListProps {
alertData?: Omit<CommentRequestAlertType, 'type'>;
disableAlerts?: boolean;
hiddenStatuses?: CaseStatusWithAllStatus[];
isSelectorView?: boolean;
onRowClick?: (theCase?: Case | SubCase) => void;
@ -64,7 +63,6 @@ export interface AllCasesListProps {
export const AllCasesList = React.memo<AllCasesListProps>(
({
alertData,
disableAlerts,
hiddenStatuses = [],
isSelectorView = false,
onRowClick,
@ -168,7 +166,6 @@ export const AllCasesList = React.memo<AllCasesListProps>(
const showActions = userCanCrud && !isSelectorView;
const columns = useCasesColumns({
disableAlerts,
dispatchUpdateCaseProperty,
filterStatus: filterOptions.status,
handleIsLoading,

View file

@ -69,7 +69,6 @@ const renderStringField = (field: string, dataTestSubj: string) =>
field != null ? <span data-test-subj={dataTestSubj}>{field}</span> : getEmptyTagValue();
export interface GetCasesColumn {
disableAlerts?: boolean;
dispatchUpdateCaseProperty: (u: UpdateCase) => void;
filterStatus: string;
handleIsLoading: (a: boolean) => void;
@ -84,7 +83,6 @@ export interface GetCasesColumn {
updateCase?: (newCase: Case) => void;
}
export const useCasesColumns = ({
disableAlerts = false,
dispatchUpdateCaseProperty,
filterStatus,
handleIsLoading,
@ -246,19 +244,15 @@ export const useCasesColumns = ({
},
truncateText: true,
},
...(!disableAlerts
? [
{
align: RIGHT_ALIGNMENT,
field: 'totalAlerts',
name: ALERTS,
render: (totalAlerts: Case['totalAlerts']) =>
totalAlerts != null
? renderStringField(`${totalAlerts}`, `case-table-column-alertsCount`)
: getEmptyTagValue(),
},
]
: []),
{
align: RIGHT_ALIGNMENT,
field: 'totalAlerts',
name: ALERTS,
render: (totalAlerts: Case['totalAlerts']) =>
totalAlerts != null
? renderStringField(`${totalAlerts}`, `case-table-column-alertsCount`)
: getEmptyTagValue(),
},
{
align: RIGHT_ALIGNMENT,
field: 'totalComment',

View file

@ -9,7 +9,7 @@ import React from 'react';
import { mount } from 'enzyme';
import { waitFor } from '@testing-library/react';
import { AllCases, AllCasesProps } from '.';
import { AllCases } from '.';
import { TestProviders } from '../../common/mock';
import { useGetTags } from '../../containers/use_get_tags';
import { useGetReporters } from '../../containers/use_get_reporters';
@ -31,10 +31,6 @@ jest.mock('../../common/lib/kibana');
jest.mock('../../containers/use_get_cases');
jest.mock('../../containers/use_get_cases_status');
const defaultAllCasesProps: AllCasesProps = {
disableAlerts: false,
};
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
const useConnectorsMock = useConnectors as jest.Mock;
const useGetCasesMock = useGetCases as jest.Mock;
@ -105,7 +101,7 @@ describe('AllCases', () => {
const wrapper = mount(
<TestProviders>
<AllCases {...defaultAllCasesProps} />
<AllCases />
</TestProviders>
);
@ -141,7 +137,7 @@ describe('AllCases', () => {
const wrapper = mount(
<TestProviders>
<AllCases {...defaultAllCasesProps} />
<AllCases />
</TestProviders>
);
@ -173,7 +169,7 @@ describe('AllCases', () => {
const wrapper = mount(
<TestProviders>
<AllCases {...defaultAllCasesProps} />
<AllCases />
</TestProviders>
);
@ -199,7 +195,7 @@ describe('AllCases', () => {
const wrapper = mount(
<TestProviders>
<AllCases {...defaultAllCasesProps} />
<AllCases />
</TestProviders>
);

View file

@ -14,11 +14,7 @@ import { getActionLicenseError } from '../use_push_to_service/helpers';
import { AllCasesList } from './all_cases_list';
import { CasesTableHeader } from './header';
export interface AllCasesProps {
disableAlerts?: boolean;
}
export const AllCases: React.FC<AllCasesProps> = ({ disableAlerts }) => {
export const AllCases: React.FC = () => {
const { userCanCrud } = useCasesContext();
const [refresh, setRefresh] = useState<number>(0);
useCasesBreadcrumbs(CasesDeepLinkId.cases);
@ -33,7 +29,7 @@ export const AllCases: React.FC<AllCasesProps> = ({ disableAlerts }) => {
return (
<>
<CasesTableHeader actionsErrors={actionsErrors} refresh={refresh} userCanCrud={userCanCrud} />
<AllCasesList disableAlerts={disableAlerts} doRefresh={doRefresh} />
<AllCasesList doRefresh={doRefresh} />
</>
);
};

View file

@ -28,14 +28,12 @@ import * as i18n from './translations';
import { useReadonlyHeader } from './use_readonly_header';
const CasesRoutesComponent: React.FC<CasesRoutesProps> = ({
disableAlerts,
onComponentInitialized,
actionsNavigation,
ruleDetailsNavigation,
showAlertDetails,
useFetchAlertData,
refreshRef,
hideSyncAlerts,
timelineIntegration,
}) => {
const { basePath, userCanCrud } = useCasesContext();
@ -51,7 +49,7 @@ const CasesRoutesComponent: React.FC<CasesRoutesProps> = ({
return (
<Switch>
<Route strict exact path={basePath}>
<AllCases disableAlerts={disableAlerts} />
<AllCases />
</Route>
<Route path={getCreateCasePath(basePath)}>
@ -59,7 +57,6 @@ const CasesRoutesComponent: React.FC<CasesRoutesProps> = ({
<CreateCase
onSuccess={onCreateCaseSuccess}
onCancel={navigateToAllCases}
disableAlerts={disableAlerts}
timelineIntegration={timelineIntegration}
/>
) : (
@ -91,7 +88,6 @@ const CasesRoutesComponent: React.FC<CasesRoutesProps> = ({
showAlertDetails={showAlertDetails}
useFetchAlertData={useFetchAlertData}
refreshRef={refreshRef}
hideSyncAlerts={hideSyncAlerts}
timelineIntegration={timelineIntegration}
/>
</Route>

View file

@ -11,7 +11,6 @@ import { CasesNavigation } from '../links';
import { CasesTimelineIntegration } from '../timeline_context';
export interface CasesRoutesProps {
disableAlerts?: boolean;
onComponentInitialized?: () => void;
actionsNavigation?: CasesNavigation<string, 'configurable'>;
ruleDetailsNavigation?: CasesNavigation<string | null | undefined, 'configurable'>;
@ -22,6 +21,5 @@ export interface CasesRoutesProps {
* **NOTE**: Do not hold on to the `.current` object, as it could become stale
*/
refreshRef?: MutableRefObject<CaseViewRefreshPropInterface>;
hideSyncAlerts?: boolean;
timelineIntegration?: CasesTimelineIntegration;
}

View file

@ -118,8 +118,8 @@ describe('CaseActionBar', () => {
it('should not show the sync alerts toggle when alerting is disabled', () => {
const { queryByText } = render(
<TestProviders>
<CaseActionBar {...defaultProps} disableAlerting={true} />
<TestProviders features={{ alerts: { sync: false } }}>
<CaseActionBar {...defaultProps} />
</TestProviders>
);

View file

@ -25,6 +25,7 @@ import { StatusContextMenu } from './status_context_menu';
import { getStatusDate, getStatusTitle } from './helpers';
import { SyncAlertsSwitch } from '../case_settings/sync_alerts_switch';
import { OnUpdateFields } from '../case_view';
import { useCasesFeatures } from '../cases_context/use_cases_features';
const MyDescriptionList = styled(EuiDescriptionList)`
${({ theme }) => css`
@ -43,7 +44,6 @@ interface CaseActionBarProps {
caseData: Case;
currentExternalIncident: CaseService | null;
userCanCrud: boolean;
disableAlerting: boolean;
isLoading: boolean;
onRefresh: () => void;
onUpdateField: (args: OnUpdateFields) => void;
@ -51,12 +51,12 @@ interface CaseActionBarProps {
const CaseActionBarComponent: React.FC<CaseActionBarProps> = ({
caseData,
currentExternalIncident,
disableAlerting,
userCanCrud,
isLoading,
onRefresh,
onUpdateField,
}) => {
const { isSyncAlertsEnabled } = useCasesFeatures();
const date = useMemo(() => getStatusDate(caseData), [caseData]);
const title = useMemo(() => getStatusTitle(caseData.status), [caseData.status]);
const onStatusChanged = useCallback(
@ -114,7 +114,7 @@ const CaseActionBarComponent: React.FC<CaseActionBarProps> = ({
responsive={false}
justifyContent="spaceBetween"
>
{userCanCrud && !disableAlerting && (
{userCanCrud && isSyncAlertsEnabled && (
<EuiFlexItem grow={false}>
<EuiDescriptionListTitle>
<EuiFlexGroup

View file

@ -61,7 +61,6 @@ export interface CaseViewComponentProps {
* **NOTE**: Do not hold on to the `.current` object, as it could become stale
*/
refreshRef?: MutableRefObject<CaseViewRefreshPropInterface>;
hideSyncAlerts?: boolean;
}
export interface CaseViewProps extends Omit<CaseViewComponentProps, 'caseId' | 'subCaseId'> {
@ -98,7 +97,6 @@ export const CaseComponent = React.memo<CaseComponentProps>(
updateCase,
useFetchAlertData,
refreshRef,
hideSyncAlerts = false,
}) => {
const { userCanCrud } = useCasesContext();
const { getCaseViewUrl } = useCaseViewNavigation();
@ -388,7 +386,6 @@ export const CaseComponent = React.memo<CaseComponentProps>(
caseData={caseData}
currentExternalIncident={currentExternalIncident}
userCanCrud={userCanCrud}
disableAlerting={ruleDetailsNavigation == null || hideSyncAlerts}
isLoading={isLoading && (updateKey === 'status' || updateKey === 'settings')}
onRefresh={handleRefresh}
onUpdateField={onUpdateField}
@ -506,7 +503,6 @@ export const CaseView = React.memo(
timelineIntegration,
useFetchAlertData,
refreshRef,
hideSyncAlerts,
}: CaseViewProps) => {
const { spaces: spacesApi } = useKibana().services;
const { detailName: caseId, subCaseId } = useCaseViewParams();
@ -562,7 +558,6 @@ export const CaseView = React.memo(
updateCase={updateCase}
useFetchAlertData={useFetchAlertData}
refreshRef={refreshRef}
hideSyncAlerts={hideSyncAlerts}
/>
</CasesTimelineIntegrationProvider>
)

View file

@ -6,40 +6,38 @@
*/
import React, { useState, useEffect } from 'react';
import { merge } from 'lodash';
import { CasesContextValue, DEFAULT_FEATURES } from '../../../common';
import { DEFAULT_BASE_PATH } from '../../common/navigation';
import { useApplication } from './use_application';
export interface CasesContextValue {
owner: string[];
appId: string;
appTitle: string;
userCanCrud: boolean;
basePath: string;
}
export const CasesContext = React.createContext<CasesContextValue | undefined>(undefined);
export interface CasesContextProps
extends Omit<CasesContextValue, 'appId' | 'appTitle' | 'basePath'> {
extends Omit<CasesContextValue, 'appId' | 'appTitle' | 'basePath' | 'features'> {
basePath?: string;
features?: Partial<CasesContextValue['features']>;
}
export interface CasesContextStateValue
extends Omit<CasesContextValue, 'appId' | 'appTitle' | 'userCanCrud'> {
export interface CasesContextStateValue extends Omit<CasesContextValue, 'appId' | 'appTitle'> {
appId?: string;
appTitle?: string;
userCanCrud?: boolean;
}
export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({
children,
value: { owner, userCanCrud, basePath = DEFAULT_BASE_PATH },
value: { owner, userCanCrud, basePath = DEFAULT_BASE_PATH, features = {} },
}) => {
const { appId, appTitle } = useApplication();
const [value, setValue] = useState<CasesContextStateValue>({
owner,
userCanCrud,
basePath,
/**
* The empty object at the beginning avoids the mutation
* of the DEFAULT_FEATURES object
*/
features: merge({}, DEFAULT_FEATURES, features),
});
/**

View file

@ -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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useMemo } from 'react';
import { useCasesContext } from './use_cases_context';
interface UseCasesFeaturesReturn {
isSyncAlertsEnabled: boolean;
}
export const useCasesFeatures = (): UseCasesFeaturesReturn => {
const { features } = useCasesContext();
const memoizedReturnValue = useMemo(
() => ({ isSyncAlertsEnabled: features.alerts.sync }),
[features]
);
return memoizedReturnValue;
};

View file

@ -17,7 +17,6 @@ export interface CreateCaseFlyoutProps {
afterCaseCreated?: (theCase: Case) => Promise<void>;
onClose: () => void;
onSuccess: (theCase: Case) => Promise<void>;
disableAlerts?: boolean;
}
const StyledFlyout = styled(EuiFlyout)`
@ -50,7 +49,7 @@ const StyledEuiFlyoutBody = styled(EuiFlyoutBody)`
overflow-y: auto;
overflow-x: hidden;
}
&& .euiFlyoutBody__overflowContent {
display: block;
padding: ${theme.eui.paddingSizes.l} ${theme.eui.paddingSizes.l} 70px;
@ -64,7 +63,7 @@ const FormWrapper = styled.div`
`;
export const CreateCaseFlyout = React.memo<CreateCaseFlyoutProps>(
({ afterCaseCreated, onClose, onSuccess, disableAlerts }) => (
({ afterCaseCreated, onClose, onSuccess }) => (
<>
<GlobalStyle />
<StyledFlyout
@ -85,7 +84,6 @@ export const CreateCaseFlyout = React.memo<CreateCaseFlyoutProps>(
onCancel={onClose}
onSuccess={onSuccess}
withSteps={false}
disableAlerts={disableAlerts}
/>
</FormWrapper>
</StyledEuiFlyoutBody>

View file

@ -44,7 +44,10 @@ const casesFormProps: CreateCaseFormProps = {
describe('CreateCaseForm', () => {
let globalForm: FormHook;
const MockHookWrapperComponent: React.FC = ({ children }) => {
const MockHookWrapperComponent: React.FC<{ testProviderProps?: unknown }> = ({
children,
testProviderProps = {},
}) => {
const { form } = useForm<FormProps>({
defaultValue: initialCaseValue,
options: { stripEmptyFields: false },
@ -54,7 +57,7 @@ describe('CreateCaseForm', () => {
globalForm = form;
return (
<TestProviders>
<TestProviders {...testProviderProps}>
<Form form={form}>{children}</Form>
</TestProviders>
);
@ -103,8 +106,8 @@ describe('CreateCaseForm', () => {
it('hides the sync alerts toggle', () => {
const { queryByText } = render(
<MockHookWrapperComponent>
<CreateCaseForm {...casesFormProps} disableAlerts />
<MockHookWrapperComponent testProviderProps={{ features: { alerts: { sync: false } } }}>
<CreateCaseForm {...casesFormProps} />
</MockHookWrapperComponent>
);
@ -118,7 +121,6 @@ describe('CreateCaseForm', () => {
<CreateCaseFormFields
connectors={[]}
isLoadingConnectors={false}
disableAlerts={false}
hideConnectorServiceNowSir={false}
withSteps={true}
/>

View file

@ -30,6 +30,7 @@ import { InsertTimeline } from '../insert_timeline';
import { UsePostComment } from '../../containers/use_post_comment';
import { SubmitCaseButton } from './submit_button';
import { FormContext } from './form_context';
import { useCasesFeatures } from '../cases_context/use_cases_features';
interface ContainerProps {
big?: boolean;
@ -51,15 +52,11 @@ const MySpinner = styled(EuiLoadingSpinner)`
export interface CreateCaseFormFieldsProps {
connectors: ActionConnector[];
isLoadingConnectors: boolean;
disableAlerts: boolean;
hideConnectorServiceNowSir: boolean;
withSteps: boolean;
}
export interface CreateCaseFormProps
extends Pick<
Partial<CreateCaseFormFieldsProps>,
'disableAlerts' | 'hideConnectorServiceNowSir' | 'withSteps'
> {
extends Pick<Partial<CreateCaseFormFieldsProps>, 'hideConnectorServiceNowSir' | 'withSteps'> {
onCancel: () => void;
onSuccess: (theCase: Case) => Promise<void>;
afterCaseCreated?: (theCase: Case, postComment: UsePostComment['postComment']) => Promise<void>;
@ -69,8 +66,10 @@ export interface CreateCaseFormProps
const empty: ActionConnector[] = [];
export const CreateCaseFormFields: React.FC<CreateCaseFormFieldsProps> = React.memo(
({ connectors, disableAlerts, isLoadingConnectors, hideConnectorServiceNowSir, withSteps }) => {
({ connectors, isLoadingConnectors, hideConnectorServiceNowSir, withSteps }) => {
const { isSubmitting } = useFormContext();
const { isSyncAlertsEnabled } = useCasesFeatures();
const firstStep = useMemo(
() => ({
title: i18n.STEP_ONE_TITLE,
@ -119,8 +118,8 @@ export const CreateCaseFormFields: React.FC<CreateCaseFormFieldsProps> = React.m
);
const allSteps = useMemo(
() => [firstStep, ...(!disableAlerts ? [secondStep] : []), thirdStep],
[disableAlerts, firstStep, secondStep, thirdStep]
() => [firstStep, ...(isSyncAlertsEnabled ? [secondStep] : []), thirdStep],
[isSyncAlertsEnabled, firstStep, secondStep, thirdStep]
);
return (
@ -135,7 +134,7 @@ export const CreateCaseFormFields: React.FC<CreateCaseFormFieldsProps> = React.m
) : (
<>
{firstStep.children}
{!disableAlerts && secondStep.children}
{isSyncAlertsEnabled && secondStep.children}
{thirdStep.children}
</>
)}
@ -148,7 +147,6 @@ CreateCaseFormFields.displayName = 'CreateCaseFormFields';
export const CreateCaseForm: React.FC<CreateCaseFormProps> = React.memo(
({
disableAlerts = false,
hideConnectorServiceNowSir = false,
withSteps = true,
afterCaseCreated,
@ -163,12 +161,9 @@ export const CreateCaseForm: React.FC<CreateCaseFormProps> = React.memo(
caseType={caseType}
hideConnectorServiceNowSir={hideConnectorServiceNowSir}
onSuccess={onSuccess}
// if we are disabling alerts, then we should not sync alerts
syncAlertsDefaultValue={!disableAlerts}
>
<CreateCaseFormFields
connectors={empty}
disableAlerts={disableAlerts}
isLoadingConnectors={false}
hideConnectorServiceNowSir={hideConnectorServiceNowSir}
withSteps={withSteps}

View file

@ -80,7 +80,6 @@ const defaultPostCase = {
const defaultCreateCaseForm: CreateCaseFormFieldsProps = {
isLoadingConnectors: false,
connectors: [],
disableAlerts: false,
withSteps: true,
hideConnectorServiceNowSir: false,
};
@ -243,16 +242,16 @@ describe('Create case', () => {
);
});
it('should set sync alerts to false when the sync setting is passed in as false and alerts are disabled', async () => {
it('should set sync alerts to false when the sync feature setting is false', async () => {
useConnectorsMock.mockReturnValue({
...sampleConnectorData,
connectors: connectorsMock,
});
const wrapper = mount(
<TestProviders>
<FormContext onSuccess={onFormSubmitSuccess} syncAlertsDefaultValue={false}>
<CreateCaseFormFields {...defaultCreateCaseForm} disableAlerts={true} />
<TestProviders features={{ alerts: { sync: false } }}>
<FormContext onSuccess={onFormSubmitSuccess}>
<CreateCaseFormFields {...defaultCreateCaseForm} />
<SubmitCaseButton />
</FormContext>
</TestProviders>

View file

@ -17,6 +17,7 @@ import { Case } from '../../containers/types';
import { CaseType } from '../../../common';
import { UsePostComment, usePostComment } from '../../containers/use_post_comment';
import { useCasesContext } from '../cases_context/use_cases_context';
import { useCasesFeatures } from '../cases_context/use_cases_features';
import { getConnectorById } from '../utils';
const initialCaseValue: FormProps = {
@ -34,7 +35,6 @@ interface Props {
children?: JSX.Element | JSX.Element[];
hideConnectorServiceNowSir?: boolean;
onSuccess?: (theCase: Case) => Promise<void>;
syncAlertsDefaultValue?: boolean;
}
export const FormContext: React.FC<Props> = ({
@ -43,10 +43,10 @@ export const FormContext: React.FC<Props> = ({
children,
hideConnectorServiceNowSir,
onSuccess,
syncAlertsDefaultValue = true,
}) => {
const { connectors, loading: isLoadingConnectors } = useConnectors();
const { owner } = useCasesContext();
const { isSyncAlertsEnabled } = useCasesFeatures();
const { postCase } = usePostCase();
const { postComment } = usePostComment();
const { pushCaseToExternalService } = usePostPushToService();
@ -56,7 +56,7 @@ export const FormContext: React.FC<Props> = ({
{
connectorId: dataConnectorId,
fields,
syncAlerts = syncAlertsDefaultValue,
syncAlerts = isSyncAlertsEnabled,
...dataWithoutConnectorId
},
isValid
@ -93,6 +93,7 @@ export const FormContext: React.FC<Props> = ({
}
},
[
isSyncAlertsEnabled,
connectors,
postCase,
caseType,
@ -101,7 +102,6 @@ export const FormContext: React.FC<Props> = ({
onSuccess,
postComment,
pushCaseToExternalService,
syncAlertsDefaultValue,
]
);

View file

@ -21,7 +21,6 @@ export const CreateCase = React.memo<CreateCaseFormProps>(
afterCaseCreated,
caseType,
hideConnectorServiceNowSir,
disableAlerts,
onCancel,
onSuccess,
timelineIntegration,
@ -40,7 +39,6 @@ export const CreateCase = React.memo<CreateCaseFormProps>(
afterCaseCreated={afterCaseCreated}
caseType={caseType}
hideConnectorServiceNowSir={hideConnectorServiceNowSir}
disableAlerts={disableAlerts}
onCancel={onCancel}
onSuccess={onSuccess}
timelineIntegration={timelineIntegration}

View file

@ -17,27 +17,24 @@ export const getCasesLazy = ({
owner,
userCanCrud,
basePath,
disableAlerts,
onComponentInitialized,
actionsNavigation,
ruleDetailsNavigation,
showAlertDetails,
useFetchAlertData,
refreshRef,
hideSyncAlerts,
timelineIntegration,
features,
}: GetCasesProps) => (
<CasesProvider value={{ owner, userCanCrud, basePath }}>
<CasesProvider value={{ owner, userCanCrud, basePath, features }}>
<Suspense fallback={<EuiLoadingSpinner />}>
<CasesLazy
disableAlerts={disableAlerts}
onComponentInitialized={onComponentInitialized}
actionsNavigation={actionsNavigation}
ruleDetailsNavigation={ruleDetailsNavigation}
showAlertDetails={showAlertDetails}
useFetchAlertData={useFetchAlertData}
refreshRef={refreshRef}
hideSyncAlerts={hideSyncAlerts}
timelineIntegration={timelineIntegration}
/>
</Suspense>

View file

@ -18,18 +18,17 @@ const CreateCaseFlyoutLazy: React.FC<CreateCaseFlyoutProps> = lazy(
export const getCreateCaseFlyoutLazy = ({
owner,
userCanCrud,
features,
afterCaseCreated,
onClose,
onSuccess,
disableAlerts,
}: GetCreateCaseFlyoutProps) => (
<CasesProvider value={{ owner, userCanCrud }}>
<CasesProvider value={{ owner, userCanCrud, features }}>
<Suspense fallback={<EuiLoadingSpinner />}>
<CreateCaseFlyoutLazy
afterCaseCreated={afterCaseCreated}
onClose={onClose}
onSuccess={onSuccess}
disableAlerts={disableAlerts}
/>
</Suspense>
</CasesProvider>

View file

@ -43,9 +43,9 @@ export const Cases = React.memo<CasesProps>(({ userCanCrud }) => {
)}
{casesUi.getCases({
basePath: CASES_PATH,
disableAlerts: true,
userCanCrud,
owner: [CASES_OWNER],
features: { alerts: { sync: false } },
useFetchAlertData,
showAlertDetails: (alertId: string) => {
setSelectedAlertId(alertId);
@ -65,7 +65,6 @@ export const Cases = React.memo<CasesProps>(({ userCanCrud }) => {
});
},
},
hideSyncAlerts: true,
})}
</>
);

View file

@ -7,7 +7,7 @@
import React, { memo, useMemo, useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { CaseStatuses, StatusAll } from '../../../../../../cases/common';
import { CaseStatuses, StatusAll, CasesContextValue } from '../../../../../../cases/common';
import { TimelineItem } from '../../../../../common/';
import { useAddToCase, normalizedEventFields } from '../../../../hooks/use_add_to_case';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
@ -24,7 +24,7 @@ export interface AddToCaseActionProps {
appId: string;
owner: string;
onClose?: Function;
disableAlerts?: boolean;
casesFeatures?: CasesContextValue['features'];
}
const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
@ -34,7 +34,7 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
appId,
owner,
onClose,
disableAlerts,
casesFeatures,
}) => {
const eventId = event?.ecs._id ?? '';
const eventIndex = event?.ecs._index ?? '';
@ -94,8 +94,8 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
onSuccess: onCaseSuccess,
useInsertTimeline,
owner: [owner],
disableAlerts,
userCanCrud: casePermissions?.crud ?? false,
features: casesFeatures,
};
}, [
attachAlertToCase,
@ -103,8 +103,8 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
onCaseSuccess,
useInsertTimeline,
owner,
disableAlerts,
casePermissions,
casesFeatures,
]);
return (

View file

@ -75,6 +75,8 @@ const ScrollableFlexItem = styled(EuiFlexItem)`
overflow: auto;
`;
const casesFeatures = { alerts: { sync: false } };
export interface TGridStandaloneProps {
appId: string;
casesOwner: string;
@ -416,7 +418,7 @@ const TGridStandaloneComponent: React.FC<TGridStandaloneProps> = ({
</EventsContainerLoading>
</TimelineContext.Provider>
) : null}
<AddToCaseAction {...addToCaseActionProps} disableAlerts />
<AddToCaseAction {...addToCaseActionProps} casesFeatures={casesFeatures} />
</AlertsTableWrapper>
</InspectButtonContainer>
);