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 committed Aug 4, 2021
1 parent 9000792 commit 536ea81
Show file tree
Hide file tree
Showing 12 changed files with 282 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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() };

Expand All @@ -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() });
Expand All @@ -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', () =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) =>
Expand Down Expand Up @@ -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(
() => ({
Expand Down Expand Up @@ -203,6 +205,7 @@ export const AllCasesGeneric = React.memo<AllCasesGenericProps>(
refreshCases,
showActions,
userCanCrud,
connectors,
});

const itemIdToExpandedRowMap = useMemo(
Expand Down
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
Expand Up @@ -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
Expand Up @@ -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';
Expand All @@ -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>
Expand Down Expand Up @@ -66,6 +74,7 @@ export interface GetCasesColumn {
refreshCases?: (a?: boolean) => void;
showActions: boolean;
userCanCrud: boolean;
connectors?: ActionConnector[];
}
export const useCasesColumns = ({
caseDetailsNavigation,
Expand All @@ -77,6 +86,7 @@ export const useCasesColumns = ({
refreshCases,
showActions,
userCanCrud,
connectors = [],
}: GetCasesColumn): CasesColumns[] => {
// Delete case
const {
Expand Down Expand Up @@ -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();
},
Expand Down Expand Up @@ -325,6 +335,7 @@ export const useCasesColumns = ({

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

const IconWrapper = styled.span`
Expand All @@ -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
Expand Down
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
Expand Up @@ -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';
Expand All @@ -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,
},
}),
};
Expand Down Expand Up @@ -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');
});

Expand Down
Loading

0 comments on commit 536ea81

Please sign in to comment.