Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Cases] Fix connector's icon bug #107633

Merged
merged 2 commits into from
Aug 4, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
60 changes: 38 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,67 @@ 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', () => {
cnasikas marked this conversation as resolved.
Show resolved Hide resolved
// If the component throws the test will fail
cnasikas marked this conversation as resolved.
Show resolved Hide resolved
mount(
<ExternalServiceColumn
theCase={useGetCasesMockState.data.cases[2]}
connectors={[
{
id: 'none',
actionTypeId: '.none',
name: 'None',
config: {},
isPreconfigured: false,
},
]}
/>
);
});
});
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(
cnasikas marked this conversation as resolved.
Show resolved Hide resolved
(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 ?? '')}
cnasikas marked this conversation as resolved.
Show resolved Hide resolved
/>
</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