Skip to content

Commit

Permalink
[ResponseOps][Cases] Add assignee user actions (#139392)
Browse files Browse the repository at this point in the history
* Adding user actions for assignees

* Fixing merge issues

* Fixing test mock errors

* Fixing display name errors and themselves

* Fixing test for time being

* Addressing feedback

* Addressing comma and uniq feedback

* Using core getUserDisplayName

* Fixing import and removing flickering

* Fixing tests

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
jonathan-buttner and kibanamachine authored Aug 31, 2022
1 parent 939f52b commit 72d0ee9
Show file tree
Hide file tree
Showing 21 changed files with 620 additions and 24 deletions.
13 changes: 8 additions & 5 deletions x-pack/plugins/cases/common/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@

import * as rt from 'io-ts';

export const UserRT = rt.type({
email: rt.union([rt.undefined, rt.null, rt.string]),
full_name: rt.union([rt.undefined, rt.null, rt.string]),
username: rt.union([rt.undefined, rt.null, rt.string]),
});
export const UserRT = rt.intersection([
rt.type({
email: rt.union([rt.undefined, rt.null, rt.string]),
full_name: rt.union([rt.undefined, rt.null, rt.string]),
username: rt.union([rt.undefined, rt.null, rt.string]),
}),
rt.partial({ profile_uid: rt.string }),
]);

export const UsersRt = rt.array(UserRT);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { useGetCaseUserActions } from '../../containers/use_get_case_user_action
import { useGetTags } from '../../containers/use_get_tags';
import { usePostPushToService } from '../../containers/use_post_push_to_service';
import { useUpdateCase } from '../../containers/use_update_case';
import { useBulkGetUserProfiles } from '../../containers/user_profiles/use_bulk_get_user_profiles';
import { CaseViewPage } from './case_view_page';
import {
caseData,
Expand All @@ -40,6 +41,7 @@ jest.mock('../../containers/use_get_tags');
jest.mock('../../containers/use_get_case');
jest.mock('../../containers/configure/use_connectors');
jest.mock('../../containers/use_post_push_to_service');
jest.mock('../../containers/user_profiles/use_bulk_get_user_profiles');
jest.mock('../user_actions/timestamp', () => ({
UserActionTimestamp: () => <></>,
}));
Expand All @@ -56,6 +58,7 @@ const useGetConnectorsMock = useGetConnectors as jest.Mock;
const usePostPushToServiceMock = usePostPushToService as jest.Mock;
const useGetCaseMetricsMock = useGetCaseMetrics as jest.Mock;
const useGetTagsMock = useGetTags as jest.Mock;
const useBulkGetUserProfilesMock = useBulkGetUserProfiles as jest.Mock;

const mockGetCase = (props: Partial<UseGetCase> = {}) => {
const data = {
Expand Down Expand Up @@ -96,6 +99,7 @@ describe('CaseViewPage', () => {
usePostPushToServiceMock.mockReturnValue({ isLoading: false, pushCaseToExternalService });
useGetConnectorsMock.mockReturnValue({ data: connectorsMock, isLoading: false });
useGetTagsMock.mockReturnValue({ data: [], isLoading: false });
useBulkGetUserProfilesMock.mockReturnValue({ data: new Map(), isLoading: false });

appMockRenderer = createAppMockRenderer();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import { EuiFlexGroup, EuiFlexItem, EuiLoadingContent } from '@elastic/eui';
import React, { useCallback, useMemo } from 'react';
import { isEqual } from 'lodash';
import { isEqual, uniq } from 'lodash';
import { useGetCurrentUserProfile } from '../../../containers/user_profiles/use_get_current_user_profile';
import { useBulkGetUserProfiles } from '../../../containers/user_profiles/use_bulk_get_user_profiles';
import { useGetConnectors } from '../../../containers/configure/use_connectors';
Expand Down Expand Up @@ -58,11 +58,14 @@ export const CaseViewActivity = ({
[caseData.assignees]
);

const { data: userProfiles, isLoading: isLoadingUserProfiles } = useBulkGetUserProfiles({
uids: assignees,
const userActionProfileUids = Array.from(userActionsData?.profileUids?.values() ?? []);
const uidsToRetrieve = uniq([...userActionProfileUids, ...assignees]);

const { data: userProfiles, isFetching: isLoadingUserProfiles } = useBulkGetUserProfiles({
uids: uidsToRetrieve,
});

const { data: currentUserProfile, isLoading: isLoadingCurrentUserProfile } =
const { data: currentUserProfile, isFetching: isLoadingCurrentUserProfile } =
useGetCurrentUserProfile();

const onShowAlertDetails = useCallback(
Expand Down Expand Up @@ -151,10 +154,12 @@ export const CaseViewActivity = ({
{isLoadingUserActions && (
<EuiLoadingContent lines={8} data-test-subj="case-view-loading-content" />
)}
{!isLoadingUserActions && userActionsData && (
{!isLoadingUserActions && userActionsData && userProfiles && (
<EuiFlexGroup direction="column" responsive={false} data-test-subj="case-view-activity">
<EuiFlexItem>
<UserActions
userProfiles={userProfiles}
currentUserProfile={currentUserProfile}
getRuleDetailsHref={ruleDetailsNavigation?.href}
onRuleDetailsClick={ruleDetailsNavigation?.onClick}
caseServices={userActionsData.caseServices}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ const SuggestUsersPopoverComponent: React.FC<SuggestUsersPopoverProps> = ({
[]
);

const { data: userProfiles, isLoading: isLoadingSuggest } = useSuggestUserProfiles({
const { data: userProfiles, isFetching: isLoadingSuggest } = useSuggestUserProfiles({
name: searchTerm,
owners: owner,
});
Expand Down Expand Up @@ -173,7 +173,7 @@ const SuggestUsersPopoverComponent: React.FC<SuggestUsersPopoverProps> = ({
searchPlaceholder: i18n.SEARCH_USERS,
clearButtonLabel: i18n.REMOVE_ASSIGNEES,
emptyMessage: <EmptyMessage />,
noMatchesMessage: <NoMatches />,
noMatchesMessage: searchResultProfiles ? <NoMatches /> : <EmptyMessage />,
}}
/>
);
Expand All @@ -183,7 +183,7 @@ SuggestUsersPopoverComponent.displayName = 'SuggestUsersPopover';

export const SuggestUsersPopover = React.memo(SuggestUsersPopoverComponent);

const sortProfiles = (profiles?: UserProfileWithAvatar[]) => {
const sortProfiles = (profiles?: UserProfileWithAvatar[] | null) => {
if (!profiles) {
return;
}
Expand Down
230 changes: 230 additions & 0 deletions x-pack/plugins/cases/public/components/user_actions/assignees.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
/*
* 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 React from 'react';
import { EuiCommentList } from '@elastic/eui';
import { render, screen } from '@testing-library/react';

import { Actions } from '../../../common/api';
import { elasticUser, getUserAction } from '../../containers/mock';
import { TestProviders } from '../../common/mock';
import { createAssigneesUserActionBuilder, shouldAddAnd, shouldAddComma } from './assignees';
import { getMockBuilderArgs } from './mock';

jest.mock('../../common/lib/kibana');
jest.mock('../../common/navigation/hooks');

describe('createAssigneesUserActionBuilder', () => {
describe('shouldAddComma', () => {
it('returns false if there are only 2 items', () => {
expect(shouldAddComma(0, 2)).toBeFalsy();
});

it('returns false it is the last items', () => {
expect(shouldAddComma(2, 3)).toBeFalsy();
});
});

describe('shouldAddAnd', () => {
it('returns false if there is only 1 item', () => {
expect(shouldAddAnd(0, 1)).toBeFalsy();
});

it('returns false it is not the last items', () => {
expect(shouldAddAnd(1, 3)).toBeFalsy();
});
});

describe('component', () => {
const builderArgs = getMockBuilderArgs();

beforeEach(() => {
jest.clearAllMocks();
});

it('renders assigned users', () => {
const userAction = getUserAction('assignees', Actions.add, {
createdBy: {
// damaged_raccoon uid
profileUid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0',
},
});
const builder = createAssigneesUserActionBuilder({
...builderArgs,
userAction,
});

const createdUserAction = builder.build();
render(
<TestProviders>
<EuiCommentList comments={createdUserAction} />
</TestProviders>
);

expect(screen.getByText('assigned')).toBeInTheDocument();
expect(screen.getByText('themselves')).toBeInTheDocument();
expect(screen.getByText('Physical Dinosaur')).toBeInTheDocument();

expect(screen.getByTestId('ua-assignee-physical_dinosaur')).toContainElement(
screen.getByText('and')
);
});

it('renders assigned users with a comma', () => {
const userAction = getUserAction('assignees', Actions.add, {
createdBy: {
// damaged_raccoon uid
profileUid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0',
},
payload: {
assignees: [
// These values map to uids in x-pack/plugins/cases/public/containers/user_profiles/api.mock.ts
{ uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0' },
{ uid: 'u_A_tM4n0wPkdiQ9smmd8o0Hr_h61XQfu8aRPh9GMoRoc_0' },
{ uid: 'u_9xDEQqUqoYCnFnPPLq5mIRHKL8gBTo_NiKgOnd5gGk0_0' },
],
},
});
const builder = createAssigneesUserActionBuilder({
...builderArgs,
userAction,
});

const createdUserAction = builder.build();
render(
<TestProviders>
<EuiCommentList comments={createdUserAction} />
</TestProviders>
);

expect(screen.getByText('assigned')).toBeInTheDocument();
expect(screen.getByText('themselves,')).toBeInTheDocument();
expect(screen.getByText('Physical Dinosaur')).toBeInTheDocument();

expect(screen.getByTestId('ua-assignee-physical_dinosaur')).toContainElement(
screen.getByText(',')
);

expect(screen.getByText('Wet Dingo')).toBeInTheDocument();
expect(screen.getByTestId('ua-assignee-wet_dingo')).toContainElement(screen.getByText('and'));
});

it('renders unassigned users', () => {
const userAction = getUserAction('assignees', Actions.delete, {
createdBy: {
// damaged_raccoon uid
profileUid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0',
},
});
const builder = createAssigneesUserActionBuilder({
...builderArgs,
userAction,
});

const createdUserAction = builder.build();
render(
<TestProviders>
<EuiCommentList comments={createdUserAction} />
</TestProviders>
);

expect(screen.getByText('unassigned')).toBeInTheDocument();
expect(screen.getByText('themselves')).toBeInTheDocument();
expect(screen.getByText('Physical Dinosaur')).toBeInTheDocument();

expect(screen.getByTestId('ua-assignee-physical_dinosaur')).toContainElement(
screen.getByText('and')
);
});

it('renders a single assigned user', () => {
const userAction = getUserAction('assignees', Actions.add, {
payload: {
assignees: [
// only render the physical dinosaur
{ uid: 'u_A_tM4n0wPkdiQ9smmd8o0Hr_h61XQfu8aRPh9GMoRoc_0' },
],
},
});
const builder = createAssigneesUserActionBuilder({
...builderArgs,
userAction,
});

const createdUserAction = builder.build();
render(
<TestProviders>
<EuiCommentList comments={createdUserAction} />
</TestProviders>
);

expect(screen.getByText('Physical Dinosaur')).toBeInTheDocument();
expect(screen.queryByText('themselves,')).not.toBeInTheDocument();
expect(screen.queryByText('and')).not.toBeInTheDocument();
});

it('renders a single assigned user that is themselves using matching profile uids', () => {
const userAction = getUserAction('assignees', Actions.add, {
createdBy: {
...elasticUser,
profileUid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0',
},
payload: {
assignees: [
// only render the damaged raccoon which is the current user
{ uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0' },
],
},
});
const builder = createAssigneesUserActionBuilder({
...builderArgs,
userAction,
});

const createdUserAction = builder.build();
render(
<TestProviders>
<EuiCommentList comments={createdUserAction} />
</TestProviders>
);

expect(screen.getByText('themselves')).toBeInTheDocument();
expect(screen.queryByText('Physical Dinosaur')).not.toBeInTheDocument();
expect(screen.queryByText('and')).not.toBeInTheDocument();
});

it('renders a single assigned user that is themselves using matching usernames', () => {
const userAction = getUserAction('assignees', Actions.add, {
createdBy: {
...elasticUser,
username: 'damaged_raccoon',
},
payload: {
assignees: [
// only render the damaged raccoon which is the current user
{ uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0' },
],
},
});
const builder = createAssigneesUserActionBuilder({
...builderArgs,
userAction,
});

const createdUserAction = builder.build();
render(
<TestProviders>
<EuiCommentList comments={createdUserAction} />
</TestProviders>
);

expect(screen.getByText('themselves')).toBeInTheDocument();
expect(screen.queryByText('Physical Dinosaur')).not.toBeInTheDocument();
expect(screen.queryByText('and')).not.toBeInTheDocument();
});
});
});
Loading

0 comments on commit 72d0ee9

Please sign in to comment.