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] Update UI to use the new connector's API #149276

Merged
merged 38 commits into from
Jan 31, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8edad49
Create getCaseConnectors hook
cnasikas Jan 19, 2023
816e8e1
Show push info
cnasikas Jan 20, 2023
c906ce4
Get fields from the new API
cnasikas Jan 20, 2023
c2eaf8c
Remove button & callout components from usePushToService
cnasikas Jan 21, 2023
71e543d
Move isValid and connectorName to edit connector component
cnasikas Jan 21, 2023
fe1d996
Reset form based on key
cnasikas Jan 22, 2023
2e6a8c6
Remove spacer on last item in CasesCallouts
cnasikas Jan 22, 2023
f9ce0d4
Move push button and callouts to seperate files
cnasikas Jan 22, 2023
085f86f
Improve styles
cnasikas Jan 22, 2023
865accf
Contruct connector on submit inside edit connector component
cnasikas Jan 22, 2023
a46c8c3
Fixes
cnasikas Jan 22, 2023
dad1ec6
Silent errors from actions client
cnasikas Jan 23, 2023
75c3c57
Minor fixes
cnasikas Jan 23, 2023
a482321
Adding oldest push timestamp
jonathan-buttner Jan 24, 2023
5d57d65
Fix tests and types
cnasikas Jan 25, 2023
db30357
Merge branch 'main' into get_connectors_info_from_server
cnasikas Jan 25, 2023
f5f4913
Merge branch 'cases-first-push-info' into get_connectors_info_from_se…
cnasikas Jan 25, 2023
7f9d189
Improve tests
cnasikas Jan 25, 2023
39162ce
Fix i18n
cnasikas Jan 25, 2023
ec5e1c7
Add more tests
cnasikas Jan 25, 2023
c50cde7
Fix bug
cnasikas Jan 25, 2023
304c450
Fix tests
cnasikas Jan 25, 2023
fb62ecc
Adding new external services field
jonathan-buttner Jan 25, 2023
d6e8fe2
Clarifying date field names
jonathan-buttner Jan 25, 2023
7296f8a
Merge branch 'main' into get_connectors_info_from_server
cnasikas Jan 25, 2023
965d17d
Merge branch 'cases-latest-push-info' into get_connectors_info_from_s…
cnasikas Jan 25, 2023
54963b3
Get external service from case connectors
cnasikas Jan 26, 2023
c31815f
Add API unit test
cnasikas Jan 26, 2023
8aee8e0
PR feedback
cnasikas Jan 26, 2023
b5b626b
Merge branch 'main' into get_connectors_info_from_server
cnasikas Jan 27, 2023
fc04401
PR feedback
cnasikas Jan 27, 2023
183a915
Fix types
cnasikas Jan 27, 2023
56611dc
Fix tests
cnasikas Jan 27, 2023
908ca2d
Merge branch 'main' into get_connectors_info_from_server
cnasikas Jan 30, 2023
5cbd246
Fix conflicts
cnasikas Jan 30, 2023
9b00d2a
Rename action connectors apis
cnasikas Jan 30, 2023
50554fe
PR feedback
cnasikas Jan 30, 2023
45eeba9
Merge branch 'main' into get_connectors_info_from_server
cnasikas Jan 31, 2023
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
2 changes: 1 addition & 1 deletion x-pack/plugins/cases/common/api/connectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export const GetCaseConnectorsResponseRt = rt.record(
rt.string,
rt.intersection([
rt.type({ needsToBePushed: rt.boolean, hasBeenPushed: rt.boolean }),
rt.partial(rt.type({ latestPushDate: rt.string }).props),
rt.partial(rt.type({ latestPushDate: rt.string, oldestPushDate: rt.string }).props),
CaseConnectorRt,
])
);
Expand Down
5 changes: 5 additions & 0 deletions x-pack/plugins/cases/common/api/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
CASE_ALERTS_URL,
CASE_COMMENT_DELETE_URL,
CASE_FIND_USER_ACTIONS_URL,
INTERNAL_CONNECTORS_URL,
} from '../constants';

export const getCaseDetailsUrl = (id: string): string => {
Expand Down Expand Up @@ -57,3 +58,7 @@ export const getCaseConfigurationDetailsUrl = (configureID: string): string => {
export const getCasesFromAlertsUrl = (alertId: string): string => {
return CASE_ALERTS_URL.replace('{alert_id}', alertId);
};

export const getCaseConnectorsUrl = (id: string): string => {
return INTERNAL_CONNECTORS_URL.replace('{case_id}', id);
};
4 changes: 2 additions & 2 deletions x-pack/plugins/cases/common/ui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import type {
CaseStatuses,
User,
ActionConnector,
CaseExternalServiceBasic,
CaseUserActionResponse,
SingleCaseMetricsResponse,
CommentResponse,
Expand All @@ -29,6 +28,7 @@ import type {
CaseSeverity,
CommentResponseExternalReferenceType,
CommentResponseTypePersistableState,
GetCaseConnectorsResponse,
} from '../api';
import type { PUSH_CASES_CAPABILITY } from '../constants';
import type { SnakeToCamelCase } from '../types';
Expand Down Expand Up @@ -77,12 +77,12 @@ export type AlertComment = SnakeToCamelCase<CommentResponseAlertsType>;
export type ExternalReferenceComment = SnakeToCamelCase<CommentResponseExternalReferenceType>;
export type PersistableComment = SnakeToCamelCase<CommentResponseTypePersistableState>;
export type CaseUserActions = SnakeToCamelCase<CaseUserActionResponse>;
export type CaseExternalService = SnakeToCamelCase<CaseExternalServiceBasic>;
export type Case = Omit<SnakeToCamelCase<CaseResponse>, 'comments'> & { comments: Comment[] };
export type Cases = Omit<SnakeToCamelCase<CasesFindResponse>, 'cases'> & { cases: Case[] };
export type CasesStatus = SnakeToCamelCase<CasesStatusResponse>;
export type CasesMetrics = SnakeToCamelCase<CasesMetricsResponse>;
export type CaseUpdateRequest = SnakeToCamelCase<CasePatchRequest>;
export type CaseConnectors = GetCaseConnectorsResponse;

export interface ResolvedCase {
case: Case;
Expand Down
34 changes: 30 additions & 4 deletions x-pack/plugins/cases/public/common/mock/connectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
* 2.0.
*/

import type { ActionConnector, ActionTypeConnector } from '../../../common/api';
import type {
ActionConnector,
ActionTypeConnector,
GetCaseConnectorsResponse,
} from '../../../common/api';

export const connectorsMock: ActionConnector[] = [
{
id: 'servicenow-1',
actionTypeId: '.servicenow',
name: 'My Connector',
name: 'My SN connector',
Copy link
Member Author

@cnasikas cnasikas Jan 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the names to be sure that the tests find the correct connector in the DOM. My Connector was the name of the none connector.

config: {
apiUrl: 'https://instance1.service-now.com',
},
Expand All @@ -21,7 +25,7 @@ export const connectorsMock: ActionConnector[] = [
{
id: 'resilient-2',
actionTypeId: '.resilient',
name: 'My Connector 2',
name: 'My Resilient connector',
config: {
apiUrl: 'https://test/',
orgId: '201',
Expand Down Expand Up @@ -52,7 +56,7 @@ export const connectorsMock: ActionConnector[] = [
{
id: 'servicenow-uses-table-api',
actionTypeId: '.servicenow',
name: 'My Connector',
name: 'My deprecated SN connector',
config: {
apiUrl: 'https://instance1.service-now.com',
usesTableApi: true,
Expand Down Expand Up @@ -118,3 +122,25 @@ export const actionTypesMock: ActionTypeConnector[] = [
supportedFeatureIds: ['alerting', 'cases'],
},
];

export const getCaseConnectorsMockResponse = (
overrides: Record<string, Partial<GetCaseConnectorsResponse[string]>> = {}
): GetCaseConnectorsResponse => {
return connectorsMock.reduce(
(acc, connector) => ({
...acc,
[connector.id]: {
id: connector.id,
name: connector.name,
type: connector.actionTypeId,
fields: null,
needsToBePushed: false,
oldestPushDate: '2023-01-17T09:46:29.813Z',
latestPushDate: '2023-01-17T09:46:29.813Z',
hasBeenPushed: true,
...overrides[connector.id],
},
}),
{}
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,6 @@ describe('CaseView actions', () => {
{...defaultProps}
currentExternalIncident={{
...basicPush,
firstPushIndex: 5,
lastPushIndex: 5,
commentsToUpdate: [],
hasDataToPush: false,
}}
/>
</TestProviders>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ import { useDeleteCases } from '../../containers/use_delete_cases';
import { ConfirmDeleteCaseModal } from '../confirm_delete_case';
import { PropertyActions } from '../property_actions';
import type { Case } from '../../../common/ui/types';
import type { CaseService } from '../../containers/use_get_case_user_actions';
import { useAllCasesNavigation } from '../../common/navigation';
import { useCasesContext } from '../cases_context/use_cases_context';
import { useCasesToast } from '../../common/use_cases_toast';

interface CaseViewActions {
caseData: Case;
currentExternalIncident: CaseService | null;
currentExternalIncident: Case['externalService'];
cnasikas marked this conversation as resolved.
Show resolved Hide resolved
}

const ActionsComponent: React.FC<CaseViewActions> = ({ caseData, currentExternalIncident }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ const useGetCaseUserActionsMock = useGetCaseUserActions as jest.Mock;
const defaultUseGetCaseUserActions = {
data: {
caseUserActions: [...caseUserActions, getAlertUserAction()],
caseServices: {},
hasDataToPush: false,
participants: [basicCase.createdBy],
},
isLoading: false,
Expand Down Expand Up @@ -70,21 +68,6 @@ describe('CaseActionBar', () => {
expect(wrapper.find(`[data-test-subj="sync-alerts-switch"]`).exists()).toBeTruthy();
expect(wrapper.find(`[data-test-subj="case-refresh"]`).exists()).toBeTruthy();
expect(wrapper.find(`[data-test-subj="case-view-actions"]`).exists()).toBeTruthy();
// no loading bar
expect(wrapper.find(`[data-test-subj="case-view-action-bar-spinner"]`).exists()).toBeFalsy();
Copy link
Member Author

@cnasikas cnasikas Jan 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imo, there is no need to show the loading spinner in the action bar only for the "View " button.

});

it('shows a loading bar when user actions are loaded', async () => {
useGetCaseUserActionsMock.mockReturnValue({
data: undefined,
isLoading: true,
});
const wrapper = mount(
<TestProviders>
<CaseActionBar {...defaultProps} />
</TestProviders>
);
expect(wrapper.find(`[data-test-subj="case-view-action-bar-spinner"]`).exists()).toBeTruthy();
});

it('should show correct status', () => {
Expand Down
166 changes: 73 additions & 93 deletions x-pack/plugins/cases/public/components/case_action_bar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiIconTip,
EuiLoadingSpinner,
} from '@elastic/eui';
import type { Case } from '../../../common/ui/types';
import type { CaseStatuses } from '../../../common/api';
import * as i18n from '../case_view/translations';
import { Actions } from './actions';
import { useGetCaseUserActions } from '../../containers/use_get_case_user_actions';
import { StatusContextMenu } from './status_context_menu';
import { SyncAlertsSwitch } from '../case_settings/sync_alerts_switch';
import type { OnUpdateFields } from '../case_view/types';
Expand Down Expand Up @@ -68,19 +66,7 @@ const CaseActionBarComponent: React.FC<CaseActionBarProps> = ({
[onUpdateField]
);

const { data: userActionsData, isLoading: isLoadingUserActions } = useGetCaseUserActions(
caseData.id,
caseData.connector.id
);

const currentExternalIncident = useMemo(
() =>
userActionsData?.caseServices != null &&
userActionsData.caseServices[caseData.connector.id] != null
? userActionsData.caseServices[caseData.connector.id]
: null,
[userActionsData?.caseServices, caseData.connector]
);
const currentExternalIncident = caseData.externalService ?? null;
cnasikas marked this conversation as resolved.
Show resolved Hide resolved

const onSyncAlertsChanged = useCallback(
(syncAlerts: boolean) =>
Expand All @@ -93,92 +79,86 @@ const CaseActionBarComponent: React.FC<CaseActionBarProps> = ({

return (
<EuiFlexGroup gutterSize="l" justifyContent="flexEnd" data-test-subj="case-action-bar-wrapper">
{isLoadingUserActions ? (
<>
cnasikas marked this conversation as resolved.
Show resolved Hide resolved
<EuiFlexItem grow={false}>
<EuiLoadingSpinner data-test-subj="case-view-action-bar-spinner" size="l" />
</EuiFlexItem>
) : (
<>
<EuiFlexItem grow={false}>
<MyDescriptionList compressed>
<EuiFlexGroup responsive={false} justifyContent="spaceBetween">
<EuiFlexItem grow={false} data-test-subj="case-view-status">
<EuiDescriptionListTitle>{i18n.STATUS}</EuiDescriptionListTitle>
<MyDescriptionList compressed>
<EuiFlexGroup responsive={false} justifyContent="spaceBetween">
<EuiFlexItem grow={false} data-test-subj="case-view-status">
<EuiDescriptionListTitle>{i18n.STATUS}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<StatusContextMenu
currentStatus={caseData.status}
disabled={!permissions.update}
isLoading={isLoading}
onStatusChanged={onStatusChanged}
/>
</EuiDescriptionListDescription>
</EuiFlexItem>
{!metricsFeatures.includes('lifespan') ? (
<EuiFlexItem grow={false}>
<EuiDescriptionListTitle>{title}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<StatusContextMenu
currentStatus={caseData.status}
disabled={!permissions.update}
isLoading={isLoading}
onStatusChanged={onStatusChanged}
<FormattedRelativePreferenceDate
data-test-subj={'case-action-bar-status-date'}
value={date}
/>
</EuiDescriptionListDescription>
</EuiFlexItem>
{!metricsFeatures.includes('lifespan') ? (
<EuiFlexItem grow={false}>
<EuiDescriptionListTitle>{title}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<FormattedRelativePreferenceDate
data-test-subj={'case-action-bar-status-date'}
value={date}
/>
</EuiDescriptionListDescription>
</EuiFlexItem>
) : null}
</EuiFlexGroup>
</MyDescriptionList>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiDescriptionList compressed>
<EuiFlexGroup
gutterSize="l"
alignItems="center"
responsive={false}
justifyContent="spaceBetween"
>
{permissions.update && isSyncAlertsEnabled && (
<EuiFlexItem grow={false}>
<EuiDescriptionListTitle>
<EuiFlexGroup
component="span"
alignItems="center"
gutterSize="xs"
responsive={false}
>
<EuiFlexItem grow={false}>
<span>{i18n.SYNC_ALERTS}</span>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIconTip content={i18n.SYNC_ALERTS_HELP} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<SyncAlertsSwitch
disabled={isLoading}
isSynced={caseData.settings.syncAlerts}
onSwitchChange={onSyncAlertsChanged}
/>
</EuiDescriptionListDescription>
</EuiFlexItem>
)}
) : null}
</EuiFlexGroup>
</MyDescriptionList>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiDescriptionList compressed>
<EuiFlexGroup
gutterSize="l"
alignItems="center"
responsive={false}
justifyContent="spaceBetween"
>
{permissions.update && isSyncAlertsEnabled && (
<EuiFlexItem grow={false}>
<span>
<EuiButtonEmpty
data-test-subj="case-refresh"
flush="left"
iconType="refresh"
onClick={refreshCaseViewPage}
<EuiDescriptionListTitle>
<EuiFlexGroup
component="span"
alignItems="center"
gutterSize="xs"
responsive={false}
>
{i18n.CASE_REFRESH}
</EuiButtonEmpty>
</span>
<EuiFlexItem grow={false}>
<span>{i18n.SYNC_ALERTS}</span>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIconTip content={i18n.SYNC_ALERTS_HELP} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<SyncAlertsSwitch
disabled={isLoading}
isSynced={caseData.settings.syncAlerts}
onSwitchChange={onSyncAlertsChanged}
/>
</EuiDescriptionListDescription>
</EuiFlexItem>
<Actions caseData={caseData} currentExternalIncident={currentExternalIncident} />
</EuiFlexGroup>
</EuiDescriptionList>
</EuiFlexItem>
</>
)}
)}
<EuiFlexItem grow={false}>
<span>
<EuiButtonEmpty
data-test-subj="case-refresh"
flush="left"
iconType="refresh"
onClick={refreshCaseViewPage}
>
{i18n.CASE_REFRESH}
</EuiButtonEmpty>
</span>
</EuiFlexItem>
<Actions caseData={caseData} currentExternalIncident={currentExternalIncident} />
</EuiFlexGroup>
</EuiDescriptionList>
</EuiFlexItem>
</>
</EuiFlexGroup>
);
};
Expand Down
Loading