Skip to content

Commit

Permalink
[Cases] Fix connector's icon bug (elastic#107633)
Browse files Browse the repository at this point in the history
  • Loading branch information
cnasikas authored and vadimkibana committed Aug 8, 2021
1 parent 2022ba1 commit ace03a0
Showing 12 changed files with 282 additions and 68 deletions.
Original file line number Diff line number Diff line change
@@ -7,20 +7,27 @@

import React from 'react';
import { mount } from 'enzyme';
import { AllCasesGeneric } from './all_cases_generic';
import { act } from 'react-dom/test-utils';

import { AllCasesGeneric } from './all_cases_generic';
import { TestProviders } from '../../common/mock';
import { useGetTags } from '../../containers/use_get_tags';
import { useGetReporters } from '../../containers/use_get_reporters';
import { useGetActionLicense } from '../../containers/use_get_action_license';
import { useConnectors } from '../../containers/configure/use_connectors';
import { useKibana } from '../../common/lib/kibana';
import { StatusAll } from '../../containers/types';
import { CaseStatuses, SECURITY_SOLUTION_OWNER } from '../../../common';
import { act } from 'react-dom/test-utils';
import { connectorsMock } from '../../containers/mock';
import { actionTypeRegistryMock } from '../../../../triggers_actions_ui/public/application/action_type_registry.mock';
import { triggersActionsUiMock } from '../../../../triggers_actions_ui/public/mocks';

jest.mock('../../containers/use_get_reporters');
jest.mock('../../containers/use_get_tags');
jest.mock('../../containers/use_get_action_license');
jest.mock('../../containers/configure/use_connectors');
jest.mock('../../containers/api');
jest.mock('../../common/lib/kibana');

const createCaseNavigation = { href: '', onClick: jest.fn() };

@@ -34,26 +41,34 @@ const alertDataMock = {
alertId: 'alert-id',
owner: SECURITY_SOLUTION_OWNER,
};

const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
const useConnectorsMock = useConnectors as jest.Mock;
const mockTriggersActionsUiService = triggersActionsUiMock.createStart();

jest.mock('../../common/lib/kibana', () => {
const originalModule = jest.requireActual('../../common/lib/kibana');
return {
...originalModule,
useKibana: () => ({
services: {
triggersActionsUi: {
actionTypeRegistry: {
get: jest.fn().mockReturnValue({
actionTypeTitle: '.jira',
iconClass: 'logoSecurity',
}),
},
},
triggersActionsUi: mockTriggersActionsUiService,
},
}),
};
});

describe('AllCasesGeneric ', () => {
const { createMockActionTypeModel } = actionTypeRegistryMock;

beforeAll(() => {
connectorsMock.forEach((connector) =>
useKibanaMock().services.triggersActionsUi.actionTypeRegistry.register(
createMockActionTypeModel({ id: connector.actionTypeId, iconClass: 'logoSecurity' })
)
);
});

beforeEach(() => {
jest.resetAllMocks();
(useGetTags as jest.Mock).mockReturnValue({ tags: ['coke', 'pepsi'], fetchTags: jest.fn() });
@@ -68,6 +83,7 @@ describe('AllCasesGeneric ', () => {
actionLicense: null,
isLoading: false,
});
useConnectorsMock.mockImplementation(() => ({ connectors: connectorsMock, loading: false }));
});

it('renders the first available status when hiddenStatus is given', () =>
Original file line number Diff line number Diff line change
@@ -37,6 +37,7 @@ import { CasesTableFilters } from './table_filters';
import { EuiBasicTableOnChange } from './types';

import { CasesTable } from './table';
import { useConnectors } from '../../containers/configure/use_connectors';

const ProgressLoader = styled(EuiProgress)`
${({ $isShow }: { $isShow: boolean }) =>
@@ -103,6 +104,7 @@ export const AllCasesGeneric = React.memo<AllCasesGenericProps>(

// Post Comment to Case
const { postComment, isLoading: isCommentUpdating } = usePostComment();
const { connectors } = useConnectors({ toastPermissionsErrors: false });

const sorting = useMemo(
() => ({
@@ -203,6 +205,7 @@ export const AllCasesGeneric = React.memo<AllCasesGenericProps>(
refreshCases,
showActions,
userCanCrud,
connectors,
});

const itemIdToExpandedRowMap = useMemo(
62 changes: 40 additions & 22 deletions x-pack/plugins/cases/public/components/all_cases/columns.test.tsx
Original file line number Diff line number Diff line change
@@ -10,51 +10,69 @@ import { mount } from 'enzyme';

import '../../common/mock/match_media';
import { ExternalServiceColumn } from './columns';

import { useGetCasesMockState } from '../../containers/mock';
import { useKibana } from '../../common/lib/kibana';
import { actionTypeRegistryMock } from '../../../../triggers_actions_ui/public/application/action_type_registry.mock';
import { connectors } from '../configure_cases/__mock__';

jest.mock('../../common/lib/kibana', () => {
const originalModule = jest.requireActual('../../common/lib/kibana');
return {
...originalModule,
useKibana: () => ({
services: {
triggersActionsUi: {
actionTypeRegistry: {
get: jest.fn().mockReturnValue({
actionTypeTitle: '.jira',
iconClass: 'logoSecurity',
}),
},
},
},
}),
};
});
jest.mock('../../common/lib/kibana');
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;

describe('ExternalServiceColumn ', () => {
const { createMockActionTypeModel } = actionTypeRegistryMock;

beforeAll(() => {
connectors.forEach((connector) =>
useKibanaMock().services.triggersActionsUi.actionTypeRegistry.register(
createMockActionTypeModel({ id: connector.actionTypeId, iconClass: 'logoSecurity' })
)
);
});

it('Not pushed render', () => {
const wrapper = mount(
<ExternalServiceColumn {...{ theCase: useGetCasesMockState.data.cases[0] }} />
<ExternalServiceColumn theCase={useGetCasesMockState.data.cases[0]} connectors={connectors} />
);
expect(
wrapper.find(`[data-test-subj="case-table-column-external-notPushed"]`).last().exists()
).toBeTruthy();
});

it('Up to date', () => {
const wrapper = mount(
<ExternalServiceColumn {...{ theCase: useGetCasesMockState.data.cases[1] }} />
<ExternalServiceColumn theCase={useGetCasesMockState.data.cases[1]} connectors={connectors} />
);
expect(
wrapper.find(`[data-test-subj="case-table-column-external-upToDate"]`).last().exists()
).toBeTruthy();
});

it('Needs update', () => {
const wrapper = mount(
<ExternalServiceColumn {...{ theCase: useGetCasesMockState.data.cases[2] }} />
<ExternalServiceColumn theCase={useGetCasesMockState.data.cases[2]} connectors={connectors} />
);
expect(
wrapper.find(`[data-test-subj="case-table-column-external-requiresUpdate"]`).last().exists()
).toBeTruthy();
});

it('it does not throw when accessing the icon if the connector type is not registered', () => {
// If the component throws the test will fail
expect(() =>
mount(
<ExternalServiceColumn
theCase={useGetCasesMockState.data.cases[2]}
connectors={[
{
id: 'none',
actionTypeId: '.none',
name: 'None',
config: {},
isPreconfigured: false,
},
]}
/>
)
).not.toThrowError();
});
});
24 changes: 20 additions & 4 deletions x-pack/plugins/cases/public/components/all_cases/columns.tsx
Original file line number Diff line number Diff line change
@@ -21,7 +21,14 @@ import {
import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services';
import styled from 'styled-components';

import { CaseStatuses, CaseType, DeleteCase, Case, SubCase } from '../../../common';
import {
CaseStatuses,
CaseType,
DeleteCase,
Case,
SubCase,
ActionConnector,
} from '../../../common';
import { getEmptyTagValue } from '../empty_value';
import { FormattedRelativePreferenceDate } from '../formatted_date';
import { CaseDetailsHrefSchema, CaseDetailsLink, CasesNavigation } from '../links';
@@ -35,6 +42,7 @@ import { ConfirmDeleteCaseModal } from '../confirm_delete_case';
import { useKibana } from '../../common/lib/kibana';
import { StatusContextMenu } from '../case_action_bar/status_context_menu';
import { TruncatedText } from '../truncated_text';
import { getConnectorIcon } from '../utils';

export type CasesColumns =
| EuiTableActionsColumnType<Case>
@@ -66,6 +74,7 @@ export interface GetCasesColumn {
refreshCases?: (a?: boolean) => void;
showActions: boolean;
userCanCrud: boolean;
connectors?: ActionConnector[];
}
export const useCasesColumns = ({
caseDetailsNavigation,
@@ -77,6 +86,7 @@ export const useCasesColumns = ({
refreshCases,
showActions,
userCanCrud,
connectors = [],
}: GetCasesColumn): CasesColumns[] => {
// Delete case
const {
@@ -266,7 +276,7 @@ export const useCasesColumns = ({
name: i18n.EXTERNAL_INCIDENT,
render: (theCase: Case) => {
if (theCase.id != null) {
return <ExternalServiceColumn theCase={theCase} />;
return <ExternalServiceColumn theCase={theCase} connectors={connectors} />;
}
return getEmptyTagValue();
},
@@ -325,6 +335,7 @@ export const useCasesColumns = ({

interface Props {
theCase: Case;
connectors: ActionConnector[];
}

const IconWrapper = styled.span`
@@ -335,26 +346,31 @@ const IconWrapper = styled.span`
width: 20px !important;
}
`;
export const ExternalServiceColumn: React.FC<Props> = ({ theCase }) => {

export const ExternalServiceColumn: React.FC<Props> = ({ theCase, connectors }) => {
const { triggersActionsUi } = useKibana().services;

if (theCase.externalService == null) {
return renderStringField(i18n.NOT_PUSHED, `case-table-column-external-notPushed`);
}

const lastPushedConnector: ActionConnector | undefined = connectors.find(
(connector) => connector.id === theCase.externalService?.connectorId
);
const lastCaseUpdate = theCase.updatedAt != null ? new Date(theCase.updatedAt) : null;
const lastCasePush =
theCase.externalService?.pushedAt != null ? new Date(theCase.externalService?.pushedAt) : null;
const hasDataToPush =
lastCasePush === null ||
(lastCaseUpdate != null && lastCasePush.getTime() < lastCaseUpdate?.getTime());

return (
<p>
<IconWrapper>
<EuiIcon
size="original"
title={theCase.externalService?.connectorName}
type={triggersActionsUi.actionTypeRegistry.get(theCase.connector.type)?.iconClass ?? ''}
type={getConnectorIcon(triggersActionsUi, lastPushedConnector?.actionTypeId)}
/>
</IconWrapper>
<EuiLink
40 changes: 30 additions & 10 deletions x-pack/plugins/cases/public/components/all_cases/index.test.tsx
Original file line number Diff line number Diff line change
@@ -9,9 +9,16 @@ import React from 'react';
import { mount } from 'enzyme';
import moment from 'moment-timezone';
import { waitFor } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';

import '../../common/mock/match_media';
import { TestProviders } from '../../common/mock';
import { casesStatus, useGetCasesMockState, collectionCase } from '../../containers/mock';
import {
casesStatus,
useGetCasesMockState,
collectionCase,
connectorsMock,
} from '../../containers/mock';

import { CaseStatuses, CaseType, SECURITY_SOLUTION_OWNER, StatusAll } from '../../../common';
import { getEmptyTagValue } from '../empty_value';
@@ -20,36 +27,38 @@ import { useGetCases } from '../../containers/use_get_cases';
import { useGetCasesStatus } from '../../containers/use_get_cases_status';
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 { AllCasesGeneric as AllCases } from './all_cases_generic';
import { AllCasesProps } from '.';
import { CasesColumns, GetCasesColumn, useCasesColumns } from './columns';
import { renderHook } from '@testing-library/react-hooks';
import { actionTypeRegistryMock } from '../../../../triggers_actions_ui/public/application/action_type_registry.mock';
import { triggersActionsUiMock } from '../../../../triggers_actions_ui/public/mocks';

jest.mock('../../containers/use_bulk_update_case');
jest.mock('../../containers/use_delete_cases');
jest.mock('../../containers/use_get_cases');
jest.mock('../../containers/use_get_cases_status');
jest.mock('../../containers/use_get_action_license');
jest.mock('../../containers/configure/use_connectors');

const useDeleteCasesMock = useDeleteCases as jest.Mock;
const useGetCasesMock = useGetCases as jest.Mock;
const useGetCasesStatusMock = useGetCasesStatus as jest.Mock;
const useUpdateCasesMock = useUpdateCases as jest.Mock;
const useGetActionLicenseMock = useGetActionLicense as jest.Mock;
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
const useConnectorsMock = useConnectors as jest.Mock;

const mockTriggersActionsUiService = triggersActionsUiMock.createStart();

jest.mock('../../common/lib/kibana', () => {
const originalModule = jest.requireActual('../../common/lib/kibana');
return {
...originalModule,
useKibana: () => ({
services: {
triggersActionsUi: {
actionTypeRegistry: {
get: jest.fn().mockReturnValue({
actionTypeTitle: '.jira',
iconClass: 'logoSecurity',
}),
},
},
triggersActionsUi: mockTriggersActionsUiService,
},
}),
};
@@ -139,13 +148,24 @@ describe('AllCasesGeneric', () => {
userCanCrud: true,
};

const { createMockActionTypeModel } = actionTypeRegistryMock;

beforeAll(() => {
connectorsMock.forEach((connector) =>
useKibanaMock().services.triggersActionsUi.actionTypeRegistry.register(
createMockActionTypeModel({ id: connector.actionTypeId, iconClass: 'logoSecurity' })
)
);
});

beforeEach(() => {
jest.clearAllMocks();
useUpdateCasesMock.mockReturnValue(defaultUpdateCases);
useGetCasesMock.mockReturnValue(defaultGetCases);
useDeleteCasesMock.mockReturnValue(defaultDeleteCases);
useGetCasesStatusMock.mockReturnValue(defaultCasesStatus);
useGetActionLicenseMock.mockReturnValue(defaultActionLicense);
useConnectorsMock.mockImplementation(() => ({ connectors: connectorsMock, loading: false }));
moment.tz.setDefault('UTC');
});

Loading

0 comments on commit ace03a0

Please sign in to comment.