mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Cases] Use the new internal users API in the UI (#150432)
## Summary This PR updates the UI to use the new `users` API introduced in https://github.com/elastic/kibana/pull/149663. I changed the backend response to accommodate the needs of the UI. ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
parent
870bc3895a
commit
4f95b7c7ed
32 changed files with 1147 additions and 578 deletions
|
@ -7,23 +7,37 @@
|
|||
|
||||
import * as rt from 'io-ts';
|
||||
|
||||
const UserWithoutProfileUidRt = 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]),
|
||||
}),
|
||||
UserWithoutProfileUidRt,
|
||||
rt.partial({ profile_uid: rt.string }),
|
||||
]);
|
||||
|
||||
export const UserWithProfileInfoRt = rt.intersection([
|
||||
rt.type({
|
||||
user: UserWithoutProfileUidRt,
|
||||
}),
|
||||
rt.partial({ uid: rt.string }),
|
||||
rt.partial({
|
||||
avatar: rt.partial({ initials: rt.string, color: rt.string, imageUrl: rt.string }),
|
||||
}),
|
||||
]);
|
||||
|
||||
export const UsersRt = rt.array(UserRt);
|
||||
|
||||
export type User = rt.TypeOf<typeof UserRt>;
|
||||
export type UserWithProfileInfo = rt.TypeOf<typeof UserWithProfileInfoRt>;
|
||||
|
||||
export const GetCaseUsersResponseRt = rt.type({
|
||||
assignees: rt.array(UserRt),
|
||||
unassignedUsers: rt.array(UserRt),
|
||||
participants: rt.array(UserRt),
|
||||
assignees: rt.array(UserWithProfileInfoRt),
|
||||
unassignedUsers: rt.array(UserWithProfileInfoRt),
|
||||
participants: rt.array(UserWithProfileInfoRt),
|
||||
reporter: UserWithProfileInfoRt,
|
||||
});
|
||||
|
||||
export type GetCaseUsersResponse = rt.TypeOf<typeof GetCaseUsersResponseRt>;
|
||||
|
|
|
@ -9,10 +9,16 @@ type SnakeToCamelCaseString<S extends string> = S extends `${infer T}_${infer U}
|
|||
? `${T}${Capitalize<SnakeToCamelCaseString<U>>}`
|
||||
: S;
|
||||
|
||||
type SnakeToCamelCaseArray<T> = T extends Array<infer ArrayItem>
|
||||
? Array<SnakeToCamelCase<ArrayItem>>
|
||||
: T;
|
||||
|
||||
export type SnakeToCamelCase<T> = T extends Record<string, unknown>
|
||||
? {
|
||||
[K in keyof T as SnakeToCamelCaseString<K & string>]: SnakeToCamelCase<T[K]>;
|
||||
}
|
||||
: T extends unknown[]
|
||||
? SnakeToCamelCaseArray<T>
|
||||
: T;
|
||||
|
||||
export enum CASE_VIEW_PAGE_TABS {
|
||||
|
|
|
@ -30,6 +30,7 @@ import type {
|
|||
CommentResponseExternalReferenceType,
|
||||
CommentResponseTypePersistableState,
|
||||
GetCaseConnectorsResponse,
|
||||
GetCaseUsersResponse,
|
||||
} from '../api';
|
||||
import type { PUSH_CASES_CAPABILITY } from '../constants';
|
||||
import type { SnakeToCamelCase } from '../types';
|
||||
|
@ -87,6 +88,7 @@ export type CasesStatus = SnakeToCamelCase<CasesStatusResponse>;
|
|||
export type CasesMetrics = SnakeToCamelCase<CasesMetricsResponse>;
|
||||
export type CaseUpdateRequest = SnakeToCamelCase<CasePatchRequest>;
|
||||
export type CaseConnectors = SnakeToCamelCase<GetCaseConnectorsResponse>;
|
||||
export type CaseUsers = GetCaseUsersResponse;
|
||||
|
||||
export interface ResolvedCase {
|
||||
case: Case;
|
||||
|
@ -147,7 +149,7 @@ export enum SortFieldCase {
|
|||
title = 'title',
|
||||
}
|
||||
|
||||
export type ElasticUser = SnakeToCamelCase<User>;
|
||||
export type CaseUser = SnakeToCamelCase<User>;
|
||||
|
||||
export interface FetchCasesProps extends ApiProps {
|
||||
queryParams?: QueryParams;
|
||||
|
|
|
@ -15,7 +15,7 @@ import { createAppMockRenderer } from '../../common/mock';
|
|||
import '../../common/mock/match_media';
|
||||
import { useCaseViewNavigation, useUrlParams } from '../../common/navigation/hooks';
|
||||
import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors';
|
||||
import { basicCaseClosed, connectorsMock } from '../../containers/mock';
|
||||
import { basicCaseClosed, connectorsMock, getCaseUsersMockResponse } from '../../containers/mock';
|
||||
import type { UseGetCase } from '../../containers/use_get_case';
|
||||
import { useGetCase } from '../../containers/use_get_case';
|
||||
import { useGetCaseMetrics } from '../../containers/use_get_case_metrics';
|
||||
|
@ -24,7 +24,7 @@ import { useGetTags } from '../../containers/use_get_tags';
|
|||
import { usePostPushToService } from '../../containers/use_post_push_to_service';
|
||||
import { useGetCaseConnectors } from '../../containers/use_get_case_connectors';
|
||||
import { useUpdateCase } from '../../containers/use_update_case';
|
||||
import { useBulkGetUserProfiles } from '../../containers/user_profiles/use_bulk_get_user_profiles';
|
||||
import { useGetCaseUsers } from '../../containers/use_get_case_users';
|
||||
import { CaseViewPage } from './case_view_page';
|
||||
import {
|
||||
caseData,
|
||||
|
@ -49,6 +49,7 @@ jest.mock('../../containers/use_get_case');
|
|||
jest.mock('../../containers/configure/use_get_supported_action_connectors');
|
||||
jest.mock('../../containers/use_post_push_to_service');
|
||||
jest.mock('../../containers/use_get_case_connectors');
|
||||
jest.mock('../../containers/use_get_case_users');
|
||||
jest.mock('../../containers/user_profiles/use_bulk_get_user_profiles');
|
||||
jest.mock('../user_actions/timestamp', () => ({
|
||||
UserActionTimestamp: () => <></>,
|
||||
|
@ -67,7 +68,7 @@ const usePostPushToServiceMock = usePostPushToService as jest.Mock;
|
|||
const useGetCaseConnectorsMock = useGetCaseConnectors as jest.Mock;
|
||||
const useGetCaseMetricsMock = useGetCaseMetrics as jest.Mock;
|
||||
const useGetTagsMock = useGetTags as jest.Mock;
|
||||
const useBulkGetUserProfilesMock = useBulkGetUserProfiles as jest.Mock;
|
||||
const useGetCaseUsersMock = useGetCaseUsers as jest.Mock;
|
||||
|
||||
const mockGetCase = (props: Partial<UseGetCase> = {}) => {
|
||||
const data = {
|
||||
|
@ -99,6 +100,7 @@ describe('CaseViewPage', () => {
|
|||
const data = caseProps.caseData;
|
||||
let appMockRenderer: AppMockRenderer;
|
||||
const caseConnectors = getCaseConnectorsMockResponse();
|
||||
const caseUsers = getCaseUsersMockResponse();
|
||||
|
||||
beforeEach(() => {
|
||||
mockGetCase();
|
||||
|
@ -113,10 +115,11 @@ describe('CaseViewPage', () => {
|
|||
});
|
||||
useGetConnectorsMock.mockReturnValue({ data: connectorsMock, isLoading: false });
|
||||
useGetTagsMock.mockReturnValue({ data: [], isLoading: false });
|
||||
useBulkGetUserProfilesMock.mockReturnValue({ data: new Map(), isLoading: false });
|
||||
const license = licensingMock.createLicense({
|
||||
license: { type: 'platinum' },
|
||||
});
|
||||
useGetCaseUsersMock.mockReturnValue({ isLoading: false, data: caseUsers });
|
||||
|
||||
appMockRenderer = createAppMockRenderer({ license });
|
||||
});
|
||||
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
import {
|
||||
alertComment,
|
||||
basicCase,
|
||||
caseUserActions,
|
||||
connectorsMock,
|
||||
getAlertUserAction,
|
||||
getCaseUsersMockResponse,
|
||||
getUserAction,
|
||||
} from '../../../containers/mock';
|
||||
import React from 'react';
|
||||
import type { AppMockRenderer } from '../../../common/mock';
|
||||
|
@ -23,12 +23,14 @@ import { useFindCaseUserActions } from '../../../containers/use_find_case_user_a
|
|||
import { usePostPushToService } from '../../../containers/use_post_push_to_service';
|
||||
import { useGetSupportedActionConnectors } from '../../../containers/configure/use_get_supported_action_connectors';
|
||||
import { useGetTags } from '../../../containers/use_get_tags';
|
||||
import { useBulkGetUserProfiles } from '../../../containers/user_profiles/use_bulk_get_user_profiles';
|
||||
import { useGetCaseConnectors } from '../../../containers/use_get_case_connectors';
|
||||
import { useGetCaseUsers } from '../../../containers/use_get_case_users';
|
||||
import { licensingMock } from '@kbn/licensing-plugin/public/mocks';
|
||||
import { waitForComponentToUpdate } from '../../../common/test_utils';
|
||||
import { waitFor } from '@testing-library/dom';
|
||||
import { waitFor, within } from '@testing-library/dom';
|
||||
import { getCaseConnectorsMockResponse } from '../../../common/mock/connectors';
|
||||
import { defaultUseFindCaseUserActions } from '../mocks';
|
||||
import { ActionTypes } from '../../../../common/api';
|
||||
|
||||
jest.mock('../../../containers/use_find_case_user_actions');
|
||||
jest.mock('../../../containers/configure/use_get_supported_action_connectors');
|
||||
|
@ -41,6 +43,7 @@ jest.mock('../../../containers/use_get_action_license');
|
|||
jest.mock('../../../containers/use_get_tags');
|
||||
jest.mock('../../../containers/user_profiles/use_bulk_get_user_profiles');
|
||||
jest.mock('../../../containers/use_get_case_connectors');
|
||||
jest.mock('../../../containers/use_get_case_users');
|
||||
|
||||
(useGetTags as jest.Mock).mockReturnValue({ data: ['coke', 'pepsi'], refetch: jest.fn() });
|
||||
|
||||
|
@ -74,39 +77,28 @@ const caseViewProps: CaseViewProps = {
|
|||
},
|
||||
],
|
||||
};
|
||||
const fetchCaseUserActions = jest.fn();
|
||||
const pushCaseToExternalService = jest.fn();
|
||||
|
||||
const defaultUseFindCaseUserActions = {
|
||||
data: {
|
||||
caseUserActions: [...caseUserActions, getAlertUserAction()],
|
||||
participants: [caseData.createdBy],
|
||||
},
|
||||
refetch: fetchCaseUserActions,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
};
|
||||
|
||||
export const caseProps = {
|
||||
...caseViewProps,
|
||||
caseData,
|
||||
fetchCaseMetrics: jest.fn(),
|
||||
};
|
||||
|
||||
const caseUsers = getCaseUsersMockResponse();
|
||||
|
||||
const useFindCaseUserActionsMock = useFindCaseUserActions as jest.Mock;
|
||||
const useGetConnectorsMock = useGetSupportedActionConnectors as jest.Mock;
|
||||
const usePostPushToServiceMock = usePostPushToService as jest.Mock;
|
||||
const useBulkGetUserProfilesMock = useBulkGetUserProfiles as jest.Mock;
|
||||
const useGetCaseConnectorsMock = useGetCaseConnectors as jest.Mock;
|
||||
const useGetCaseUsersMock = useGetCaseUsers as jest.Mock;
|
||||
|
||||
describe('Case View Page activity tab', () => {
|
||||
const caseConnectors = getCaseConnectorsMockResponse();
|
||||
|
||||
beforeAll(() => {
|
||||
useFindCaseUserActionsMock.mockReturnValue(defaultUseFindCaseUserActions);
|
||||
useGetConnectorsMock.mockReturnValue({ data: connectorsMock, isLoading: false });
|
||||
usePostPushToServiceMock.mockReturnValue({ isLoading: false, pushCaseToExternalService });
|
||||
useBulkGetUserProfilesMock.mockReturnValue({ isLoading: false, data: new Map() });
|
||||
useGetCaseConnectorsMock.mockReturnValue({
|
||||
isLoading: false,
|
||||
data: caseConnectors,
|
||||
|
@ -124,6 +116,8 @@ describe('Case View Page activity tab', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
appMockRender = createAppMockRenderer();
|
||||
useFindCaseUserActionsMock.mockReturnValue(defaultUseFindCaseUserActions);
|
||||
useGetCaseUsersMock.mockReturnValue({ isLoading: false, data: caseUsers });
|
||||
});
|
||||
|
||||
it('should render the activity content and main components', async () => {
|
||||
|
@ -218,4 +212,244 @@ describe('Case View Page activity tab', () => {
|
|||
expect(result.getByTestId('case-view-edit-connector')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Case users', () => {
|
||||
describe('Participants', () => {
|
||||
it('should render the participants correctly', async () => {
|
||||
appMockRender = createAppMockRenderer();
|
||||
const result = appMockRender.render(<CaseViewActivity {...caseProps} />);
|
||||
const participantsSection = within(result.getByTestId('case-view-user-list-participants'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(participantsSection.getByText('Participant 1')).toBeInTheDocument();
|
||||
expect(participantsSection.getByText('participant_2@elastic.co')).toBeInTheDocument();
|
||||
expect(participantsSection.getByText('participant_3')).toBeInTheDocument();
|
||||
expect(participantsSection.getByText('P4')).toBeInTheDocument();
|
||||
expect(participantsSection.getByText('Participant 5')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render Unknown users correctly', async () => {
|
||||
appMockRender = createAppMockRenderer();
|
||||
const result = appMockRender.render(<CaseViewActivity {...caseProps} />);
|
||||
|
||||
const participantsSection = within(result.getByTestId('case-view-user-list-participants'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(participantsSection.getByText('Unknown')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render assignees in the participants section', async () => {
|
||||
appMockRender = createAppMockRenderer();
|
||||
const result = appMockRender.render(<CaseViewActivity {...caseProps} />);
|
||||
|
||||
const participantsSection = within(result.getByTestId('case-view-user-list-participants'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(participantsSection.getByText('Unknown')).toBeInTheDocument();
|
||||
expect(participantsSection.getByText('Fuzzy Marten')).toBeInTheDocument();
|
||||
expect(participantsSection.getByText('elastic')).toBeInTheDocument();
|
||||
expect(participantsSection.getByText('Misty Mackerel')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Reporter', () => {
|
||||
it('should render the reporter correctly', async () => {
|
||||
appMockRender = createAppMockRenderer();
|
||||
const result = appMockRender.render(<CaseViewActivity {...caseProps} />);
|
||||
const reporterSection = within(result.getByTestId('case-view-user-list-reporter'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(reporterSection.getByText('Reporter 1')).toBeInTheDocument();
|
||||
expect(reporterSection.getByText('R1')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render a reporter without uid correctly', async () => {
|
||||
useGetCaseUsersMock.mockReturnValue({
|
||||
isLoading: false,
|
||||
data: {
|
||||
...caseUsers,
|
||||
reporter: {
|
||||
user: {
|
||||
email: 'reporter_no_uid@elastic.co',
|
||||
full_name: 'Reporter No UID',
|
||||
username: 'reporter_no_uid',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
appMockRender = createAppMockRenderer();
|
||||
const result = appMockRender.render(<CaseViewActivity {...caseProps} />);
|
||||
const reporterSection = within(result.getByTestId('case-view-user-list-reporter'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(reporterSection.getByText('Reporter No UID')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('fallbacks to the caseData reporter correctly', async () => {
|
||||
useGetCaseUsersMock.mockReturnValue({
|
||||
isLoading: false,
|
||||
data: null,
|
||||
});
|
||||
|
||||
appMockRender = createAppMockRenderer();
|
||||
const result = appMockRender.render(<CaseViewActivity {...caseProps} />);
|
||||
const reporterSection = within(result.getByTestId('case-view-user-list-reporter'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(reporterSection.getByText('Leslie Knope')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Assignees', () => {
|
||||
it('should render assignees in the participants section', async () => {
|
||||
appMockRender = createAppMockRenderer({ license: platinumLicense });
|
||||
const result = appMockRender.render(
|
||||
<CaseViewActivity
|
||||
{...caseProps}
|
||||
caseData={{
|
||||
...caseProps.caseData,
|
||||
assignees: caseUsers.assignees.map((assignee) => ({
|
||||
uid: assignee.uid ?? 'not-valid',
|
||||
})),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const assigneesSection = within(await result.findByTestId('case-view-assignees'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(assigneesSection.getByText('Unknown')).toBeInTheDocument();
|
||||
expect(assigneesSection.getByText('Fuzzy Marten')).toBeInTheDocument();
|
||||
expect(assigneesSection.getByText('elastic')).toBeInTheDocument();
|
||||
expect(assigneesSection.getByText('Misty Mackerel')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('User actions', () => {
|
||||
it('renders the descriptions user correctly', async () => {
|
||||
appMockRender = createAppMockRenderer();
|
||||
const result = appMockRender.render(<CaseViewActivity {...caseProps} />);
|
||||
|
||||
const description = within(result.getByTestId('description-action'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(description.getByText('Leslie Knope')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the unassigned users correctly', async () => {
|
||||
useFindCaseUserActionsMock.mockReturnValue({
|
||||
...defaultUseFindCaseUserActions,
|
||||
data: {
|
||||
userActions: [getUserAction(ActionTypes.assignees, 'delete')],
|
||||
},
|
||||
});
|
||||
|
||||
appMockRender = createAppMockRenderer();
|
||||
const result = appMockRender.render(<CaseViewActivity {...caseProps} />);
|
||||
|
||||
const userActions = within(result.getByTestId('user-actions'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(userActions.getByText('cases_no_connectors')).toBeInTheDocument();
|
||||
expect(userActions.getByText('Valid Chimpanzee')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the assigned users correctly', async () => {
|
||||
useFindCaseUserActionsMock.mockReturnValue({
|
||||
...defaultUseFindCaseUserActions,
|
||||
data: {
|
||||
userActions: [
|
||||
getUserAction(ActionTypes.assignees, 'add', {
|
||||
payload: {
|
||||
assignees: [
|
||||
{ uid: 'not-valid' },
|
||||
{ uid: 'u_3OgKOf-ogtr8kJ5B0fnRcqzXs2aQQkZLtzKEEFnKaYg_0' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
appMockRender = createAppMockRenderer();
|
||||
const result = appMockRender.render(<CaseViewActivity {...caseProps} />);
|
||||
|
||||
const userActions = within(result.getByTestId('user-actions'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(userActions.getByText('Fuzzy Marten')).toBeInTheDocument();
|
||||
expect(userActions.getByText('Unknown')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the user action users correctly', async () => {
|
||||
useFindCaseUserActionsMock.mockReturnValue({
|
||||
...defaultUseFindCaseUserActions,
|
||||
data: {
|
||||
userActions: [
|
||||
getUserAction('description', 'create'),
|
||||
getUserAction('description', 'update', {
|
||||
createdBy: {
|
||||
...caseUsers.participants[0].user,
|
||||
fullName: caseUsers.participants[0].user.full_name,
|
||||
profileUid: caseUsers.participants[0].uid,
|
||||
},
|
||||
}),
|
||||
getUserAction('comment', 'update', {
|
||||
createdBy: {
|
||||
...caseUsers.participants[1].user,
|
||||
fullName: caseUsers.participants[1].user.full_name,
|
||||
profileUid: caseUsers.participants[1].uid,
|
||||
},
|
||||
}),
|
||||
getUserAction('description', 'update', {
|
||||
createdBy: {
|
||||
...caseUsers.participants[2].user,
|
||||
fullName: caseUsers.participants[2].user.full_name,
|
||||
profileUid: caseUsers.participants[2].uid,
|
||||
},
|
||||
}),
|
||||
getUserAction('title', 'update', {
|
||||
createdBy: {
|
||||
...caseUsers.participants[3].user,
|
||||
fullName: caseUsers.participants[3].user.full_name,
|
||||
profileUid: caseUsers.participants[3].uid,
|
||||
},
|
||||
}),
|
||||
getUserAction('tags', 'add', {
|
||||
createdBy: {
|
||||
...caseUsers.participants[4].user,
|
||||
fullName: caseUsers.participants[4].user.full_name,
|
||||
profileUid: caseUsers.participants[4].uid,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
appMockRender = createAppMockRenderer();
|
||||
const result = appMockRender.render(<CaseViewActivity {...caseProps} />);
|
||||
|
||||
const userActions = within(result.getByTestId('user-actions'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(userActions.getByText('Participant 1')).toBeInTheDocument();
|
||||
expect(userActions.getByText('participant_2@elastic.co')).toBeInTheDocument();
|
||||
expect(userActions.getByText('participant_3')).toBeInTheDocument();
|
||||
expect(userActions.getByText('P4')).toBeInTheDocument();
|
||||
expect(userActions.getByText('Participant 5')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,15 +9,15 @@
|
|||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLoadingContent } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { isEqual, uniq } from 'lodash';
|
||||
import { isEqual } from 'lodash';
|
||||
import type { UserProfileWithAvatar } from '@kbn/user-profile-components';
|
||||
import { useGetCaseUsers } from '../../../containers/use_get_case_users';
|
||||
import { useGetCaseConnectors } from '../../../containers/use_get_case_connectors';
|
||||
import { useCasesFeatures } from '../../../common/use_cases_features';
|
||||
import { useGetCurrentUserProfile } from '../../../containers/user_profiles/use_get_current_user_profile';
|
||||
import { useBulkGetUserProfiles } from '../../../containers/user_profiles/use_bulk_get_user_profiles';
|
||||
import { useGetSupportedActionConnectors } from '../../../containers/configure/use_get_supported_action_connectors';
|
||||
import type { CaseSeverity } from '../../../../common/api';
|
||||
import { useCaseViewNavigation } from '../../../common/navigation';
|
||||
import type { UseFetchAlertData } from '../../../../common/ui/types';
|
||||
import type { CaseUsers, UseFetchAlertData } from '../../../../common/ui/types';
|
||||
import type { Case, CaseStatuses } from '../../../../common';
|
||||
import { EditConnector } from '../../edit_connector';
|
||||
import type { CasesNavigation } from '../../links';
|
||||
|
@ -32,6 +32,41 @@ import { SeveritySidebarSelector } from '../../severity/sidebar_selector';
|
|||
import { useFindCaseUserActions } from '../../../containers/use_find_case_user_actions';
|
||||
import { AssignUsers } from './assign_users';
|
||||
import type { Assignee } from '../../user_profiles/types';
|
||||
import { convertToCaseUserWithProfileInfo } from '../../user_profiles/user_converter';
|
||||
|
||||
const buildUserProfilesMap = (users?: CaseUsers): Map<string, UserProfileWithAvatar> => {
|
||||
const userProfiles = new Map();
|
||||
|
||||
if (!users) {
|
||||
return userProfiles;
|
||||
}
|
||||
|
||||
for (const user of [
|
||||
...users.assignees,
|
||||
...users.participants,
|
||||
users.reporter,
|
||||
...users.unassignedUsers,
|
||||
]) {
|
||||
/**
|
||||
* If the user has a valid profile UID and a valid username
|
||||
* then the backend successfully fetched the user profile
|
||||
* information from the security plugin. Checking only for the
|
||||
* profile UID is not enough as a user can use our API to add
|
||||
* an assignee with a non existing UID.
|
||||
*/
|
||||
if (user.uid != null && user.user.username != null) {
|
||||
userProfiles.set(user.uid, {
|
||||
uid: user.uid,
|
||||
user: user.user,
|
||||
data: {
|
||||
avatar: user.avatar,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return userProfiles;
|
||||
};
|
||||
|
||||
export const CaseViewActivity = ({
|
||||
ruleDetailsNavigation,
|
||||
|
@ -47,7 +82,6 @@ export const CaseViewActivity = ({
|
|||
useFetchAlertData: UseFetchAlertData;
|
||||
}) => {
|
||||
const { permissions } = useCasesContext();
|
||||
const { getCaseViewUrl } = useCaseViewNavigation();
|
||||
const { caseAssignmentAuthorized, pushToServiceAuthorized } = useCasesFeatures();
|
||||
|
||||
const { data: caseConnectors, isLoading: isLoadingCaseConnectors } = useGetCaseConnectors(
|
||||
|
@ -58,18 +92,15 @@ export const CaseViewActivity = ({
|
|||
caseData.id
|
||||
);
|
||||
|
||||
const { data: caseUsers, isLoading: isLoadingCaseUsers } = useGetCaseUsers(caseData.id);
|
||||
|
||||
const userProfiles = buildUserProfilesMap(caseUsers);
|
||||
|
||||
const assignees = useMemo(
|
||||
() => caseData.assignees.map((assignee) => assignee.uid),
|
||||
[caseData.assignees]
|
||||
);
|
||||
|
||||
const userActionProfileUids = Array.from(userActionsData?.profileUids?.values() ?? []);
|
||||
const uidsToRetrieve = uniq([...userActionProfileUids, ...assignees]);
|
||||
|
||||
const { data: userProfiles, isFetching: isLoadingUserProfiles } = useBulkGetUserProfiles({
|
||||
uids: uidsToRetrieve,
|
||||
});
|
||||
|
||||
const { data: currentUserProfile, isFetching: isLoadingCurrentUserProfile } =
|
||||
useGetCurrentUserProfile();
|
||||
|
||||
|
@ -87,9 +118,7 @@ export const CaseViewActivity = ({
|
|||
});
|
||||
|
||||
const isLoadingAssigneeData =
|
||||
(isLoading && loadingKey === 'assignees') ||
|
||||
isLoadingUserProfiles ||
|
||||
isLoadingCurrentUserProfile;
|
||||
(isLoading && loadingKey === 'assignees') || isLoadingCaseUsers || isLoadingCurrentUserProfile;
|
||||
|
||||
const changeStatus = useCallback(
|
||||
(status: CaseStatuses) =>
|
||||
|
@ -100,14 +129,6 @@ export const CaseViewActivity = ({
|
|||
[onUpdateField]
|
||||
);
|
||||
|
||||
const emailContent = useMemo(
|
||||
() => ({
|
||||
subject: i18n.EMAIL_SUBJECT(caseData.title),
|
||||
body: i18n.EMAIL_BODY(getCaseViewUrl({ detailName: caseData.id })),
|
||||
}),
|
||||
[caseData.title, getCaseViewUrl, caseData.id]
|
||||
);
|
||||
|
||||
const onSubmitTags = useCallback(
|
||||
(newTags) => onUpdateField({ key: 'tags', value: newTags }),
|
||||
[onUpdateField]
|
||||
|
@ -148,11 +169,16 @@ export const CaseViewActivity = ({
|
|||
!isLoadingCaseConnectors &&
|
||||
userActionsData &&
|
||||
caseConnectors &&
|
||||
userProfiles;
|
||||
caseUsers;
|
||||
|
||||
const showConnectorSidebar =
|
||||
pushToServiceAuthorized && userActionsData && caseConnectors && supportedActionConnectors;
|
||||
|
||||
const reporterAsArray =
|
||||
caseUsers?.reporter != null
|
||||
? [caseUsers.reporter]
|
||||
: [convertToCaseUserWithProfileInfo(caseData.createdBy)];
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexItem grow={6}>
|
||||
|
@ -168,7 +194,7 @@ export const CaseViewActivity = ({
|
|||
getRuleDetailsHref={ruleDetailsNavigation?.href}
|
||||
onRuleDetailsClick={ruleDetailsNavigation?.onClick}
|
||||
caseConnectors={caseConnectors}
|
||||
caseUserActions={userActionsData.caseUserActions}
|
||||
caseUserActions={userActionsData.userActions}
|
||||
data={caseData}
|
||||
actionsNavigation={actionsNavigation}
|
||||
isLoadingDescription={isLoading && loadingKey === 'description'}
|
||||
|
@ -211,18 +237,18 @@ export const CaseViewActivity = ({
|
|||
/>
|
||||
<UserList
|
||||
dataTestSubj="case-view-user-list-reporter"
|
||||
email={emailContent}
|
||||
theCase={caseData}
|
||||
headline={i18n.REPORTER}
|
||||
users={[caseData.createdBy]}
|
||||
users={reporterAsArray}
|
||||
userProfiles={userProfiles}
|
||||
/>
|
||||
{userActionsData?.participants ? (
|
||||
{caseUsers != null ? (
|
||||
<UserList
|
||||
dataTestSubj="case-view-user-list-participants"
|
||||
email={emailContent}
|
||||
theCase={caseData}
|
||||
headline={i18n.PARTICIPANTS}
|
||||
loading={isLoadingUserActions}
|
||||
users={userActionsData.participants}
|
||||
users={[...caseUsers.participants, ...caseUsers.assignees]}
|
||||
userProfiles={userProfiles}
|
||||
/>
|
||||
) : null}
|
||||
|
|
|
@ -6,36 +6,82 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { UserList } from './user_list';
|
||||
import * as i18n from '../translations';
|
||||
import { basicCase } from '../../../containers/mock';
|
||||
import { useCaseViewNavigation } from '../../../common/navigation/hooks';
|
||||
import type { AppMockRenderer } from '../../../common/mock';
|
||||
import { createAppMockRenderer } from '../../../common/mock';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
jest.mock('../../../common/navigation/hooks');
|
||||
|
||||
const useCaseViewNavigationMock = useCaseViewNavigation as jest.Mock;
|
||||
|
||||
describe('UserList ', () => {
|
||||
const title = 'Case Title';
|
||||
const caseLink = 'http://reddit.com';
|
||||
const user = { username: 'username', fullName: 'Full Name', email: 'testemail@elastic.co' };
|
||||
const title = basicCase.title;
|
||||
const caseLink = 'https://example.com/cases/test';
|
||||
const user = {
|
||||
email: 'case_all@elastic.co',
|
||||
fullName: 'Cases',
|
||||
username: 'cases_all',
|
||||
};
|
||||
|
||||
const open = jest.fn();
|
||||
const getCaseViewUrl = jest.fn().mockReturnValue(caseLink);
|
||||
let appMockRender: AppMockRenderer;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
appMockRender = createAppMockRenderer();
|
||||
useCaseViewNavigationMock.mockReturnValue({ getCaseViewUrl });
|
||||
window.open = open;
|
||||
});
|
||||
|
||||
it('triggers mailto when email icon clicked', () => {
|
||||
const wrapper = shallow(
|
||||
const result = appMockRender.render(
|
||||
<UserList
|
||||
email={{
|
||||
subject: i18n.EMAIL_SUBJECT(title),
|
||||
body: i18n.EMAIL_BODY(caseLink),
|
||||
}}
|
||||
theCase={basicCase}
|
||||
headline={i18n.REPORTER}
|
||||
users={[user]}
|
||||
users={[
|
||||
{
|
||||
user: { ...user, full_name: user.fullName },
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
wrapper.find('[data-test-subj="user-list-email-button"]').simulate('click');
|
||||
|
||||
userEvent.click(result.getByTestId('user-list-email-button'));
|
||||
|
||||
expect(open).toBeCalledWith(
|
||||
`mailto:${user.email}?subject=${i18n.EMAIL_SUBJECT(title)}&body=${i18n.EMAIL_BODY(caseLink)}`,
|
||||
'_blank'
|
||||
);
|
||||
});
|
||||
|
||||
it('sort the users correctly', () => {
|
||||
const result = appMockRender.render(
|
||||
<UserList
|
||||
theCase={basicCase}
|
||||
headline={i18n.REPORTER}
|
||||
users={[
|
||||
{
|
||||
user: { ...user, full_name: 'Cases' },
|
||||
},
|
||||
{
|
||||
user: { ...user, username: 'elastic', email: 'elastic@elastic.co', full_name: null },
|
||||
},
|
||||
{
|
||||
user: { ...user, username: 'test', full_name: null, email: null },
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
const userProfiles = result.getAllByTestId('user-profile-username');
|
||||
|
||||
expect(userProfiles[0].textContent).toBe('Cases');
|
||||
expect(userProfiles[1].textContent).toBe('elastic@elastic.co');
|
||||
expect(userProfiles[2].textContent).toBe('test');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import React, { useCallback } from 'react';
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import { sortBy } from 'lodash';
|
||||
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
|
@ -20,20 +21,19 @@ import {
|
|||
import styled, { css } from 'styled-components';
|
||||
|
||||
import type { UserProfileWithAvatar } from '@kbn/user-profile-components';
|
||||
import type { ElasticUser } from '../../../containers/types';
|
||||
import { useCaseViewNavigation } from '../../../common/navigation';
|
||||
import type { Case } from '../../../containers/types';
|
||||
import * as i18n from '../translations';
|
||||
import type { UserInfoWithAvatar } from '../../user_profiles/types';
|
||||
import type { CaseUserWithProfileInfo, UserInfoWithAvatar } from '../../user_profiles/types';
|
||||
import { HoverableUserWithAvatar } from '../../user_profiles/hoverable_user_with_avatar';
|
||||
import { convertToUserInfo } from '../../user_profiles/user_converter';
|
||||
import { getSortField } from '../../user_profiles/sort';
|
||||
|
||||
interface UserListProps {
|
||||
email: {
|
||||
subject: string;
|
||||
body: string;
|
||||
};
|
||||
theCase: Case;
|
||||
headline: string;
|
||||
loading?: boolean;
|
||||
users: ElasticUser[];
|
||||
users: CaseUserWithProfileInfo[];
|
||||
userProfiles?: Map<string, UserProfileWithAvatar>;
|
||||
dataTestSubj?: string;
|
||||
}
|
||||
|
@ -67,8 +67,18 @@ const renderUsers = (
|
|||
</MyFlexGroup>
|
||||
));
|
||||
|
||||
const getEmailContent = ({ caseTitle, caseUrl }: { caseTitle: string; caseUrl: string }) => ({
|
||||
subject: i18n.EMAIL_SUBJECT(caseTitle),
|
||||
body: i18n.EMAIL_BODY(caseUrl),
|
||||
});
|
||||
|
||||
export const UserList: React.FC<UserListProps> = React.memo(
|
||||
({ email, headline, loading, users, userProfiles, dataTestSubj }) => {
|
||||
({ theCase, userProfiles, headline, loading, users, dataTestSubj }) => {
|
||||
const { getCaseViewUrl } = useCaseViewNavigation();
|
||||
|
||||
const caseUrl = getCaseViewUrl({ detailName: theCase.id });
|
||||
const email = getEmailContent({ caseTitle: theCase.title, caseUrl });
|
||||
|
||||
const handleSendEmail = useCallback(
|
||||
(emailAddress: string | undefined | null) => {
|
||||
if (emailAddress && emailAddress != null) {
|
||||
|
@ -82,8 +92,9 @@ export const UserList: React.FC<UserListProps> = React.memo(
|
|||
);
|
||||
|
||||
const validUsers = getValidUsers(users, userProfiles ?? new Map());
|
||||
const orderedUsers = sortBy(validUsers, getSortField);
|
||||
|
||||
if (validUsers.length === 0) {
|
||||
if (orderedUsers.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -99,7 +110,7 @@ export const UserList: React.FC<UserListProps> = React.memo(
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
{renderUsers(validUsers, handleSendEmail)}
|
||||
{renderUsers(orderedUsers, handleSendEmail)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
|
@ -109,11 +120,19 @@ export const UserList: React.FC<UserListProps> = React.memo(
|
|||
UserList.displayName = 'UserList';
|
||||
|
||||
const getValidUsers = (
|
||||
users: ElasticUser[],
|
||||
users: CaseUserWithProfileInfo[],
|
||||
userProfiles: Map<string, UserProfileWithAvatar>
|
||||
): UserInfoWithAvatar[] => {
|
||||
const validUsers = users.reduce<Map<string, UserInfoWithAvatar>>((acc, user) => {
|
||||
const convertedUser = convertToUserInfo(user, userProfiles);
|
||||
const userCamelCase = {
|
||||
email: user.user.email,
|
||||
fullName: user.user.full_name,
|
||||
username: user.user.username,
|
||||
profileUid: user.uid,
|
||||
};
|
||||
|
||||
const convertedUser = convertToUserInfo(userCamelCase, userProfiles);
|
||||
|
||||
if (convertedUser != null) {
|
||||
acc.set(convertedUser.key, convertedUser.userInfo);
|
||||
}
|
||||
|
|
|
@ -100,10 +100,7 @@ export const defaultUpdateCaseState = {
|
|||
};
|
||||
|
||||
export const defaultUseFindCaseUserActions = {
|
||||
data: {
|
||||
caseUserActions: [...caseUserActions, getAlertUserAction()],
|
||||
participants: [caseData.createdBy],
|
||||
},
|
||||
data: { userActions: [...caseUserActions, getAlertUserAction()] },
|
||||
refetch: jest.fn(),
|
||||
isLoading: false,
|
||||
isFetching: false,
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import type { ElasticUser } from '../../containers/types';
|
||||
import type { CaseUser } from '../../containers/types';
|
||||
import { userProfiles, userProfilesMap } from '../../containers/user_profiles/api.mock';
|
||||
import { HoverableAvatarResolver } from './hoverable_avatar_resolver';
|
||||
|
||||
describe('HoverableAvatarResolver', () => {
|
||||
it('renders the avatar using the elastic user information when the profile is not present', async () => {
|
||||
const user: ElasticUser = {
|
||||
const user: CaseUser = {
|
||||
username: 'user',
|
||||
email: 'some.user@google.com',
|
||||
fullName: 'Some Super User',
|
||||
|
@ -25,7 +25,7 @@ describe('HoverableAvatarResolver', () => {
|
|||
});
|
||||
|
||||
it('renders the avatar when the profile uid is not found', async () => {
|
||||
const user: ElasticUser = {
|
||||
const user: CaseUser = {
|
||||
username: 'some_user',
|
||||
profileUid: '123',
|
||||
fullName: null,
|
||||
|
@ -38,7 +38,7 @@ describe('HoverableAvatarResolver', () => {
|
|||
});
|
||||
|
||||
it('renders the avatar using the profile', async () => {
|
||||
const user: ElasticUser = {
|
||||
const user: CaseUser = {
|
||||
username: userProfiles[0].user.username,
|
||||
profileUid: userProfiles[0].uid,
|
||||
fullName: null,
|
||||
|
@ -51,7 +51,7 @@ describe('HoverableAvatarResolver', () => {
|
|||
});
|
||||
|
||||
it('renders the tooltip when hovering', async () => {
|
||||
const user: ElasticUser = {
|
||||
const user: CaseUser = {
|
||||
username: userProfiles[0].user.username,
|
||||
profileUid: userProfiles[0].uid,
|
||||
fullName: null,
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
import type { UserProfileWithAvatar } from '@kbn/user-profile-components';
|
||||
import React from 'react';
|
||||
import type { ElasticUser } from '../../containers/types';
|
||||
import type { CaseUser } from '../../containers/types';
|
||||
import { convertToUserInfo } from './user_converter';
|
||||
import { HoverableAvatar } from './hoverable_avatar';
|
||||
|
||||
const HoverableAvatarResolverComponent: React.FC<{
|
||||
user: ElasticUser;
|
||||
user: CaseUser;
|
||||
userProfiles?: Map<string, UserProfileWithAvatar>;
|
||||
}> = ({ user, userProfiles }) => {
|
||||
const { userInfo } = convertToUserInfo(user, userProfiles) ?? { userInfo: undefined };
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import type { ElasticUser } from '../../containers/types';
|
||||
import type { CaseUser } from '../../containers/types';
|
||||
import { userProfiles, userProfilesMap } from '../../containers/user_profiles/api.mock';
|
||||
import { HoverableUserWithAvatarResolver } from './hoverable_user_with_avatar_resolver';
|
||||
|
||||
describe('HoverableUserWithAvatarResolver', () => {
|
||||
it('renders the avatar and display name using the full name', async () => {
|
||||
const user: ElasticUser = {
|
||||
const user: CaseUser = {
|
||||
username: 'user',
|
||||
email: 'some.user@google.com',
|
||||
fullName: 'Some Super User',
|
||||
|
@ -26,7 +26,7 @@ describe('HoverableUserWithAvatarResolver', () => {
|
|||
});
|
||||
|
||||
it('renders the avatar and display name using the username when the profile uid is not found', async () => {
|
||||
const user: ElasticUser = {
|
||||
const user: CaseUser = {
|
||||
username: 'some_user',
|
||||
profileUid: '123',
|
||||
fullName: null,
|
||||
|
@ -40,7 +40,7 @@ describe('HoverableUserWithAvatarResolver', () => {
|
|||
});
|
||||
|
||||
it('renders the avatar and display name using the profile', async () => {
|
||||
const user: ElasticUser = {
|
||||
const user: CaseUser = {
|
||||
username: userProfiles[0].user.username,
|
||||
profileUid: userProfiles[0].uid,
|
||||
fullName: null,
|
||||
|
@ -54,7 +54,7 @@ describe('HoverableUserWithAvatarResolver', () => {
|
|||
});
|
||||
|
||||
it('renders display name bolded by default', async () => {
|
||||
const user: ElasticUser = {
|
||||
const user: CaseUser = {
|
||||
username: userProfiles[0].user.username,
|
||||
profileUid: userProfiles[0].uid,
|
||||
fullName: null,
|
||||
|
|
|
@ -7,14 +7,14 @@
|
|||
|
||||
import type { UserProfileWithAvatar } from '@kbn/user-profile-components';
|
||||
import React from 'react';
|
||||
import type { ElasticUser } from '../../containers/types';
|
||||
import type { CaseUser } from '../../containers/types';
|
||||
import type { HoverableUserWithAvatarProps } from './hoverable_user_with_avatar';
|
||||
import { HoverableUserWithAvatar } from './hoverable_user_with_avatar';
|
||||
import { convertToUserInfo } from './user_converter';
|
||||
|
||||
const HoverableUserWithAvatarResolverComponent: React.FC<
|
||||
{
|
||||
user: ElasticUser;
|
||||
user: CaseUser;
|
||||
userProfiles?: Map<string, UserProfileWithAvatar>;
|
||||
} & Pick<HoverableUserWithAvatarProps, 'boldName'>
|
||||
> = ({ user, userProfiles, boldName = true }) => {
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import type { ElasticUser } from '../../containers/types';
|
||||
import type { CaseUser } from '../../containers/types';
|
||||
import { userProfiles, userProfilesMap } from '../../containers/user_profiles/api.mock';
|
||||
import { HoverableUsernameResolver } from './hoverable_username_resolver';
|
||||
|
||||
describe('HoverableUsernameResolver', () => {
|
||||
it('renders the full name using the elastic user information when the profile is not present', async () => {
|
||||
const user: ElasticUser = {
|
||||
const user: CaseUser = {
|
||||
username: 'user',
|
||||
email: 'some.user@google.com',
|
||||
fullName: 'Some Super User',
|
||||
|
@ -25,7 +25,7 @@ describe('HoverableUsernameResolver', () => {
|
|||
});
|
||||
|
||||
it('renders the username when the profile uid is not found', async () => {
|
||||
const user: ElasticUser = {
|
||||
const user: CaseUser = {
|
||||
username: 'some_user',
|
||||
profileUid: '123',
|
||||
fullName: null,
|
||||
|
@ -38,7 +38,7 @@ describe('HoverableUsernameResolver', () => {
|
|||
});
|
||||
|
||||
it('renders the full name using the profile', async () => {
|
||||
const user: ElasticUser = {
|
||||
const user: CaseUser = {
|
||||
username: userProfiles[0].user.username,
|
||||
profileUid: userProfiles[0].uid,
|
||||
fullName: null,
|
||||
|
@ -51,7 +51,7 @@ describe('HoverableUsernameResolver', () => {
|
|||
});
|
||||
|
||||
it('renders the full name bolded by default', async () => {
|
||||
const user: ElasticUser = {
|
||||
const user: CaseUser = {
|
||||
username: userProfiles[0].user.username,
|
||||
profileUid: userProfiles[0].uid,
|
||||
fullName: null,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import type { UserProfileWithAvatar } from '@kbn/user-profile-components';
|
||||
import React from 'react';
|
||||
import type { ElasticUser } from '../../containers/types';
|
||||
import type { CaseUser } from '../../containers/types';
|
||||
import type { HoverableUserWithAvatarProps } from './hoverable_user_with_avatar';
|
||||
import { Username } from './username';
|
||||
import { convertToUserInfo } from './user_converter';
|
||||
|
@ -15,7 +15,7 @@ import { UserToolTip } from './user_tooltip';
|
|||
|
||||
const HoverableUsernameResolverComponent: React.FC<
|
||||
{
|
||||
user: ElasticUser;
|
||||
user: CaseUser;
|
||||
userProfiles?: Map<string, UserProfileWithAvatar>;
|
||||
} & Pick<HoverableUserWithAvatarProps, 'boldName'>
|
||||
> = ({ user, userProfiles, boldName = true }) => {
|
||||
|
|
|
@ -9,12 +9,14 @@ import type { UserProfileWithAvatar } from '@kbn/user-profile-components';
|
|||
import { sortBy } from 'lodash';
|
||||
import { NO_ASSIGNEES_VALUE } from '../all_cases/assignees_filter';
|
||||
import type { CurrentUserProfile } from '../types';
|
||||
import type { AssigneesFilteringSelection } from './types';
|
||||
import { UNKNOWN } from './translations';
|
||||
import type { AssigneesFilteringSelection, UserInfoWithAvatar } from './types';
|
||||
|
||||
export const getSortField = (profile: UserProfileWithAvatar) =>
|
||||
profile.user.full_name?.toLowerCase() ??
|
||||
profile.user.email?.toLowerCase() ??
|
||||
profile.user.username.toLowerCase();
|
||||
export const getSortField = (profile: UserProfileWithAvatar | UserInfoWithAvatar) =>
|
||||
profile.user?.full_name?.toLowerCase() ??
|
||||
profile.user?.email?.toLowerCase() ??
|
||||
profile.user?.username.toLowerCase() ??
|
||||
UNKNOWN;
|
||||
|
||||
export const moveCurrentUserToBeginning = <T extends { uid: string }>(
|
||||
currentUserProfile?: T,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { UserProfileWithAvatar } from '@kbn/user-profile-components';
|
||||
import type { UserWithProfileInfo } from '../../../common/api';
|
||||
|
||||
export interface Assignee {
|
||||
uid: string;
|
||||
|
@ -18,3 +19,4 @@ export interface AssigneeWithProfile extends Assignee {
|
|||
|
||||
export type UserInfoWithAvatar = Partial<Pick<UserProfileWithAvatar, 'user' | 'data'>>;
|
||||
export type AssigneesFilteringSelection = UserProfileWithAvatar | null;
|
||||
export type CaseUserWithProfileInfo = UserWithProfileInfo;
|
||||
|
|
|
@ -6,80 +6,104 @@
|
|||
*/
|
||||
|
||||
import { userProfiles, userProfilesMap } from '../../containers/user_profiles/api.mock';
|
||||
import { convertToUserInfo } from './user_converter';
|
||||
import { convertToCaseUserWithProfileInfo, convertToUserInfo } from './user_converter';
|
||||
|
||||
describe('convertToUserInfo', () => {
|
||||
it('returns undefined when the username is an empty string and the profile uid is not defined', () => {
|
||||
expect(convertToUserInfo({ username: '', email: null, fullName: null })).toBeUndefined();
|
||||
});
|
||||
describe('user_converter', () => {
|
||||
describe('convertToUserInfo', () => {
|
||||
it('returns undefined when the username is an empty string and the profile uid is not defined', () => {
|
||||
expect(convertToUserInfo({ username: '', email: null, fullName: null })).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns a key of 123 and empty user info when the username is an empty string and the profile uid is not found', () => {
|
||||
expect(
|
||||
convertToUserInfo({ username: '', profileUid: '123', email: null, fullName: null })
|
||||
).toEqual({
|
||||
key: '123',
|
||||
userInfo: {},
|
||||
it('returns a key of 123 and empty user info when the username is an empty string and the profile uid is not found', () => {
|
||||
expect(
|
||||
convertToUserInfo({ username: '', profileUid: '123', email: null, fullName: null })
|
||||
).toEqual({
|
||||
key: '123',
|
||||
userInfo: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the profile uid as the key and full profile when the profile uid is found', () => {
|
||||
expect(
|
||||
convertToUserInfo(
|
||||
{ profileUid: userProfiles[0].uid, email: null, fullName: null, username: null },
|
||||
userProfilesMap
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"key": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
|
||||
"userInfo": Object {
|
||||
"data": Object {},
|
||||
"enabled": true,
|
||||
"uid": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
|
||||
"user": Object {
|
||||
"email": "damaged_raccoon@elastic.co",
|
||||
"full_name": "Damaged Raccoon",
|
||||
"username": "damaged_raccoon",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('returns the username as the key and the user info using the existing elastic user information', () => {
|
||||
expect(convertToUserInfo({ username: 'sam', fullName: 'Sam Smith', email: 'sam@sam.com' }))
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"key": "sam",
|
||||
"userInfo": Object {
|
||||
"user": Object {
|
||||
"email": "sam@sam.com",
|
||||
"full_name": "Sam Smith",
|
||||
"username": "sam",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('returns the username as the key and the user info using the existing elastic user information when the profile uid is not found', () => {
|
||||
expect(
|
||||
convertToUserInfo({
|
||||
username: 'sam',
|
||||
fullName: 'Sam Smith',
|
||||
email: 'sam@sam.com',
|
||||
profileUid: '123',
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"key": "sam",
|
||||
"userInfo": Object {
|
||||
"user": Object {
|
||||
"email": "sam@sam.com",
|
||||
"full_name": "Sam Smith",
|
||||
"username": "sam",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the profile uid as the key and full profile when the profile uid is found', () => {
|
||||
expect(
|
||||
convertToUserInfo(
|
||||
{ profileUid: userProfiles[0].uid, email: null, fullName: null, username: null },
|
||||
userProfilesMap
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"key": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
|
||||
"userInfo": Object {
|
||||
"data": Object {},
|
||||
"enabled": true,
|
||||
"uid": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
|
||||
describe('convertToCaseUserWithProfileInfo', () => {
|
||||
it('converts a CaseUse to a CaseUserWithProfileInfo correctly', () => {
|
||||
expect(
|
||||
convertToCaseUserWithProfileInfo({
|
||||
username: 'test',
|
||||
email: 'test@elastic.co',
|
||||
fullName: 'Test',
|
||||
profileUid: 'test-id',
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"uid": "test-id",
|
||||
"user": Object {
|
||||
"email": "damaged_raccoon@elastic.co",
|
||||
"full_name": "Damaged Raccoon",
|
||||
"username": "damaged_raccoon",
|
||||
"email": "test@elastic.co",
|
||||
"full_name": "Test",
|
||||
"username": "test",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('returns the username as the key and the user info using the existing elastic user information', () => {
|
||||
expect(convertToUserInfo({ username: 'sam', fullName: 'Sam Smith', email: 'sam@sam.com' }))
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"key": "sam",
|
||||
"userInfo": Object {
|
||||
"user": Object {
|
||||
"email": "sam@sam.com",
|
||||
"full_name": "Sam Smith",
|
||||
"username": "sam",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('returns the username as the key and the user info using the existing elastic user information when the profile uid is not found', () => {
|
||||
expect(
|
||||
convertToUserInfo({
|
||||
username: 'sam',
|
||||
fullName: 'Sam Smith',
|
||||
email: 'sam@sam.com',
|
||||
profileUid: '123',
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"key": "sam",
|
||||
"userInfo": Object {
|
||||
"user": Object {
|
||||
"email": "sam@sam.com",
|
||||
"full_name": "Sam Smith",
|
||||
"username": "sam",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
|
||||
import type { UserProfileWithAvatar } from '@kbn/user-profile-components';
|
||||
import { isEmpty } from 'lodash';
|
||||
import type { ElasticUser } from '../../containers/types';
|
||||
import type { UserInfoWithAvatar } from './types';
|
||||
import type { CaseUser } from '../../containers/types';
|
||||
import type { CaseUserWithProfileInfo, UserInfoWithAvatar } from './types';
|
||||
|
||||
export const convertToUserInfo = (
|
||||
user: ElasticUser,
|
||||
user: CaseUser,
|
||||
userProfiles?: Map<string, UserProfileWithAvatar>
|
||||
): { key: string; userInfo: UserInfoWithAvatar } | undefined => {
|
||||
const username = user.username;
|
||||
|
@ -35,7 +35,7 @@ export const convertToUserInfo = (
|
|||
|
||||
const isValidString = (value?: string | null): value is string => !isEmpty(value);
|
||||
|
||||
const createWithUsername = (username: string, user: ElasticUser) => {
|
||||
const createWithUsername = (username: string, user: CaseUser) => {
|
||||
return {
|
||||
key: username,
|
||||
userInfo: {
|
||||
|
@ -43,3 +43,8 @@ const createWithUsername = (username: string, user: ElasticUser) => {
|
|||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const convertToCaseUserWithProfileInfo = (user: CaseUser): CaseUserWithProfileInfo => ({
|
||||
uid: user.profileUid,
|
||||
user: { email: user.email, full_name: user.fullName, username: user.username },
|
||||
});
|
||||
|
|
|
@ -26,8 +26,14 @@ import {
|
|||
pushedCase,
|
||||
tags,
|
||||
findCaseUserActionsResponse,
|
||||
getCaseUsersMockResponse,
|
||||
} from '../mock';
|
||||
import type { CaseConnectors, CaseUpdateRequest, ResolvedCase } from '../../../common/ui/types';
|
||||
import type {
|
||||
CaseConnectors,
|
||||
CaseUpdateRequest,
|
||||
CaseUsers,
|
||||
ResolvedCase,
|
||||
} from '../../../common/ui/types';
|
||||
import { SeverityAll } from '../../../common/ui/types';
|
||||
import type {
|
||||
CasePatchRequest,
|
||||
|
@ -146,3 +152,6 @@ export const getCaseConnectors = async (
|
|||
caseId: string,
|
||||
signal: AbortSignal
|
||||
): Promise<CaseConnectors> => Promise.resolve(getCaseConnectorsMockResponse());
|
||||
|
||||
export const getCaseUsers = async (caseId: string, signal: AbortSignal): Promise<CaseUsers> =>
|
||||
Promise.resolve(getCaseUsersMockResponse());
|
||||
|
|
|
@ -14,6 +14,7 @@ import type {
|
|||
FetchCasesProps,
|
||||
ResolvedCase,
|
||||
FindCaseUserActions,
|
||||
CaseUsers,
|
||||
} from '../../common/ui/types';
|
||||
import { SeverityAll, SortFieldCase, StatusAll } from '../../common/ui/types';
|
||||
import type {
|
||||
|
@ -39,6 +40,7 @@ import {
|
|||
getCaseFindUserActionsUrl,
|
||||
getCaseCommentDeleteUrl,
|
||||
getCaseConnectorsUrl,
|
||||
getCaseUsersUrl,
|
||||
} from '../../common/api';
|
||||
import {
|
||||
CASE_REPORTERS_URL,
|
||||
|
@ -406,3 +408,10 @@ export const getCaseConnectors = async (
|
|||
{}
|
||||
);
|
||||
};
|
||||
|
||||
export const getCaseUsers = async (caseId: string, signal: AbortSignal): Promise<CaseUsers> => {
|
||||
return KibanaServices.get().http.fetch<CaseUsers>(getCaseUsersUrl(caseId), {
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ElasticUser } from '../types';
|
||||
import type { CaseUser } from '../types';
|
||||
import type {
|
||||
ActionConnector,
|
||||
ActionTypeConnector,
|
||||
|
@ -38,11 +38,11 @@ export interface CaseConfigure {
|
|||
closureType: ClosureType;
|
||||
connector: CasesConfigure['connector'];
|
||||
createdAt: string;
|
||||
createdBy: ElasticUser;
|
||||
createdBy: CaseUser;
|
||||
error: string | null;
|
||||
mappings: CaseConnectorMapping[];
|
||||
updatedAt: string;
|
||||
updatedBy: ElasticUser;
|
||||
updatedBy: CaseUser;
|
||||
version: string;
|
||||
owner: string;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ export const casesQueriesKeys = {
|
|||
caseMetrics: (id: string, features: SingleCaseMetricsFeature[]) =>
|
||||
[...casesQueriesKeys.case(id), 'metrics', features] as const,
|
||||
caseConnectors: (id: string) => [...casesQueriesKeys.case(id), 'connectors'],
|
||||
caseUsers: (id: string) => [...casesQueriesKeys.case(id), 'users'],
|
||||
userActions: (id: string) => [...casesQueriesKeys.case(id), 'user-actions'] as const,
|
||||
userProfiles: () => [...casesQueriesKeys.users, 'user-profiles'] as const,
|
||||
userProfilesList: (ids: string[]) => [...casesQueriesKeys.userProfiles(), ids] as const,
|
||||
|
|
|
@ -16,6 +16,7 @@ import type {
|
|||
ExternalReferenceComment,
|
||||
PersistableComment,
|
||||
FindCaseUserActions,
|
||||
CaseUsers,
|
||||
} from '../../common/ui/types';
|
||||
import type {
|
||||
CaseConnector,
|
||||
|
@ -957,3 +958,110 @@ export const getPersistableStateAttachment = (
|
|||
...viewObject,
|
||||
}),
|
||||
});
|
||||
|
||||
export const getCaseUsersMockResponse = (): CaseUsers => {
|
||||
return {
|
||||
participants: [
|
||||
{
|
||||
user: {
|
||||
email: 'participant_1@elastic.co',
|
||||
full_name: 'Participant 1',
|
||||
username: 'participant_1',
|
||||
},
|
||||
},
|
||||
{
|
||||
user: {
|
||||
email: 'participant_2@elastic.co',
|
||||
full_name: null,
|
||||
username: 'participant_2',
|
||||
},
|
||||
},
|
||||
{
|
||||
user: {
|
||||
email: null,
|
||||
full_name: null,
|
||||
username: 'participant_3',
|
||||
},
|
||||
},
|
||||
{
|
||||
user: {
|
||||
email: null,
|
||||
full_name: null,
|
||||
username: 'participant_4',
|
||||
},
|
||||
uid: 'participant_4_uid',
|
||||
avatar: { initials: 'P4' },
|
||||
},
|
||||
{
|
||||
user: {
|
||||
email: 'participant_5@elastic.co',
|
||||
full_name: 'Participant 5',
|
||||
username: 'participant_5',
|
||||
},
|
||||
uid: 'participant_5_uid',
|
||||
},
|
||||
],
|
||||
reporter: {
|
||||
user: {
|
||||
email: 'reporter_1@elastic.co',
|
||||
full_name: 'Reporter 1',
|
||||
username: 'reporter_1',
|
||||
},
|
||||
uid: 'reporter_1_uid',
|
||||
avatar: { initials: 'R1' },
|
||||
},
|
||||
|
||||
assignees: [
|
||||
{
|
||||
user: {
|
||||
email: null,
|
||||
full_name: null,
|
||||
username: null,
|
||||
},
|
||||
uid: 'u_62h24XVQzG4-MuH1-DqPmookrJY23aRa9h4fyULR6I8_0',
|
||||
},
|
||||
{
|
||||
user: {
|
||||
email: null,
|
||||
full_name: null,
|
||||
username: 'elastic',
|
||||
},
|
||||
uid: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0',
|
||||
},
|
||||
{
|
||||
user: {
|
||||
email: 'fuzzy_marten@profiles.elastic.co',
|
||||
full_name: 'Fuzzy Marten',
|
||||
username: 'fuzzy_marten',
|
||||
},
|
||||
uid: 'u_3OgKOf-ogtr8kJ5B0fnRcqzXs2aQQkZLtzKEEFnKaYg_0',
|
||||
},
|
||||
{
|
||||
user: {
|
||||
email: 'misty_mackerel@profiles.elastic.co',
|
||||
full_name: 'Misty Mackerel',
|
||||
username: 'misty_mackerel',
|
||||
},
|
||||
uid: 'u_BXf_iGxcnicv4l-3-ER7I-XPpfanAFAap7uls86xV7A_0',
|
||||
},
|
||||
],
|
||||
unassignedUsers: [
|
||||
{
|
||||
user: {
|
||||
email: '',
|
||||
full_name: '',
|
||||
username: 'cases_no_connectors',
|
||||
},
|
||||
uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0',
|
||||
},
|
||||
{
|
||||
user: {
|
||||
email: 'valid_chimpanzee@profiles.elastic.co',
|
||||
full_name: 'Valid Chimpanzee',
|
||||
username: 'valid_chimpanzee',
|
||||
},
|
||||
uid: 'u_A_tM4n0wPkdiQ9smmd8o0Hr_h61XQfu8aRPh9GMoRoc_0',
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
|
|
@ -8,14 +8,7 @@
|
|||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import type { UseFindCaseUserActions } from './use_find_case_user_actions';
|
||||
import { useFindCaseUserActions } from './use_find_case_user_actions';
|
||||
import {
|
||||
basicCase,
|
||||
caseUserActions,
|
||||
elasticUser,
|
||||
findCaseUserActionsResponse,
|
||||
getUserAction,
|
||||
} from './mock';
|
||||
import { Actions } from '../../common/api';
|
||||
import { basicCase, findCaseUserActionsResponse } from './mock';
|
||||
import React from 'react';
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { testQueryClient } from '../common/mock';
|
||||
|
@ -53,9 +46,10 @@ describe('UseFindCaseUserActions', () => {
|
|||
expect.objectContaining({
|
||||
...initialData,
|
||||
data: {
|
||||
caseUserActions: [...findCaseUserActionsResponse.userActions],
|
||||
participants: [elasticUser],
|
||||
profileUids: new Set(),
|
||||
userActions: [...findCaseUserActionsResponse.userActions],
|
||||
total: 20,
|
||||
perPage: 1000,
|
||||
page: 1,
|
||||
},
|
||||
isError: false,
|
||||
isLoading: false,
|
||||
|
@ -80,137 +74,4 @@ describe('UseFindCaseUserActions', () => {
|
|||
expect(spy).toHaveBeenCalledWith(basicCase.id, expect.any(AbortSignal));
|
||||
expect(addError).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('getProfileUids', () => {
|
||||
it('aggregates the uids from the createdBy field of a user action', async () => {
|
||||
jest.spyOn(api, 'findCaseUserActions').mockReturnValue(
|
||||
Promise.resolve({
|
||||
page: 1,
|
||||
perPage: 1000,
|
||||
total: 20,
|
||||
userActions: [getUserAction('pushed', Actions.add, { createdBy: { profileUid: '456' } })],
|
||||
})
|
||||
);
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook<string, UseFindCaseUserActions>(
|
||||
() => useFindCaseUserActions(basicCase.id),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current.data?.profileUids).toMatchInlineSnapshot(`
|
||||
Set {
|
||||
"456",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('aggregates the uids from a push', async () => {
|
||||
jest.spyOn(api, 'findCaseUserActions').mockReturnValue(
|
||||
Promise.resolve({
|
||||
page: 1,
|
||||
perPage: 1000,
|
||||
total: 20,
|
||||
userActions: [
|
||||
getUserAction('pushed', Actions.add, {
|
||||
payload: { externalService: { pushedBy: { profileUid: '123' } } },
|
||||
}),
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook<string, UseFindCaseUserActions>(
|
||||
() => useFindCaseUserActions(basicCase.id),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current.data?.profileUids).toMatchInlineSnapshot(`
|
||||
Set {
|
||||
"123",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('aggregates the uids from an assignment add user action', async () => {
|
||||
jest.spyOn(api, 'findCaseUserActions').mockReturnValue(
|
||||
Promise.resolve({
|
||||
page: 1,
|
||||
perPage: 1000,
|
||||
total: 20,
|
||||
userActions: [...caseUserActions, getUserAction('assignees', Actions.add)],
|
||||
})
|
||||
);
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook<string, UseFindCaseUserActions>(
|
||||
() => useFindCaseUserActions(basicCase.id),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current.data?.profileUids).toMatchInlineSnapshot(`
|
||||
Set {
|
||||
"u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
|
||||
"u_A_tM4n0wPkdiQ9smmd8o0Hr_h61XQfu8aRPh9GMoRoc_0",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('ignores duplicate uids', async () => {
|
||||
jest.spyOn(api, 'findCaseUserActions').mockReturnValue(
|
||||
Promise.resolve({
|
||||
page: 1,
|
||||
perPage: 1000,
|
||||
total: 20,
|
||||
userActions: [
|
||||
...caseUserActions,
|
||||
getUserAction('assignees', Actions.add),
|
||||
getUserAction('assignees', Actions.add),
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook<string, UseFindCaseUserActions>(
|
||||
() => useFindCaseUserActions(basicCase.id),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current.data?.profileUids).toMatchInlineSnapshot(`
|
||||
Set {
|
||||
"u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
|
||||
"u_A_tM4n0wPkdiQ9smmd8o0Hr_h61XQfu8aRPh9GMoRoc_0",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('aggregates the uids from an assignment delete user action', async () => {
|
||||
jest.spyOn(api, 'findCaseUserActions').mockReturnValue(
|
||||
Promise.resolve({
|
||||
page: 1,
|
||||
perPage: 1000,
|
||||
total: 20,
|
||||
userActions: [...caseUserActions, getUserAction('assignees', Actions.delete)],
|
||||
})
|
||||
);
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook<string, UseFindCaseUserActions>(
|
||||
() => useFindCaseUserActions(basicCase.id),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current.data?.profileUids).toMatchInlineSnapshot(`
|
||||
Set {
|
||||
"u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
|
||||
"u_A_tM4n0wPkdiQ9smmd8o0Hr_h61XQfu8aRPh9GMoRoc_0",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,64 +5,22 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { isEmpty, uniqBy } from 'lodash/fp';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { CaseUserActions } from '../../common/ui/types';
|
||||
import { ActionTypes } from '../../common/api';
|
||||
import type { FindCaseUserActions } from '../../common/ui/types';
|
||||
import { findCaseUserActions } from './api';
|
||||
import { isPushedUserAction } from '../../common/utils/user_actions';
|
||||
import type { ServerError } from '../types';
|
||||
import { useToasts } from '../common/lib/kibana';
|
||||
import { ERROR_TITLE } from './translations';
|
||||
import { casesQueriesKeys } from './constants';
|
||||
|
||||
export const getProfileUids = (userActions: CaseUserActions[]) => {
|
||||
const uids = userActions.reduce<Set<string>>((acc, userAction) => {
|
||||
if (userAction.type === ActionTypes.assignees) {
|
||||
const uidsFromPayload = userAction.payload.assignees.map((assignee) => assignee.uid);
|
||||
for (const uid of uidsFromPayload) {
|
||||
acc.add(uid);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
isPushedUserAction<'camelCase'>(userAction) &&
|
||||
userAction.payload.externalService.pushedBy.profileUid != null
|
||||
) {
|
||||
acc.add(userAction.payload.externalService.pushedBy.profileUid);
|
||||
}
|
||||
|
||||
if (userAction.createdBy.profileUid != null) {
|
||||
acc.add(userAction.createdBy.profileUid);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, new Set());
|
||||
|
||||
return uids;
|
||||
};
|
||||
|
||||
export const useFindCaseUserActions = (caseId: string) => {
|
||||
const toasts = useToasts();
|
||||
const abortCtrlRef = new AbortController();
|
||||
|
||||
return useQuery(
|
||||
return useQuery<FindCaseUserActions, ServerError>(
|
||||
casesQueriesKeys.userActions(caseId),
|
||||
async () => {
|
||||
const response = await findCaseUserActions(caseId, abortCtrlRef.signal);
|
||||
const participants = !isEmpty(response.userActions)
|
||||
? uniqBy('createdBy.username', response.userActions).map((cau) => cau.createdBy)
|
||||
: [];
|
||||
|
||||
const caseUserActions = !isEmpty(response.userActions) ? response.userActions : [];
|
||||
const profileUids = getProfileUids(caseUserActions);
|
||||
|
||||
return {
|
||||
caseUserActions,
|
||||
participants,
|
||||
profileUids,
|
||||
};
|
||||
return findCaseUserActions(caseId, abortCtrlRef.signal);
|
||||
},
|
||||
{
|
||||
onError: (error: ServerError) => {
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { useQuery } from '@tanstack/react-query';
|
||||
import { casesQueriesKeys } from './constants';
|
||||
import type { ServerError } from '../types';
|
||||
import { useCasesToast } from '../common/use_cases_toast';
|
||||
import { getCaseUsers } from './api';
|
||||
import * as i18n from './translations';
|
||||
import type { CaseUsers } from './types';
|
||||
|
||||
export const useGetCaseUsers = (caseId: string) => {
|
||||
const { showErrorToast } = useCasesToast();
|
||||
|
||||
return useQuery<CaseUsers, ServerError>(
|
||||
casesQueriesKeys.caseUsers(caseId),
|
||||
() => {
|
||||
const abortCtrlRef = new AbortController();
|
||||
return getCaseUsers(caseId, abortCtrlRef.signal);
|
||||
},
|
||||
{
|
||||
onError: (error: ServerError) => {
|
||||
showErrorToast(error, { title: i18n.ERROR_TITLE });
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export type UseGetCaseUsers = ReturnType<typeof useGetCaseUsers>;
|
|
@ -9,6 +9,7 @@ import { uniqBy, isEmpty } from 'lodash';
|
|||
import type { UserProfile } from '@kbn/security-plugin/common';
|
||||
import type { IBasePath } from '@kbn/core-http-browser';
|
||||
import type { SecurityPluginStart } from '@kbn/security-plugin/server';
|
||||
import type { UserProfileWithAvatar } from '@kbn/user-profile-components';
|
||||
import { CASE_VIEW_PAGE_TABS } from '../../../common/types';
|
||||
import { isPushedUserAction } from '../../../common/utils/user_actions';
|
||||
import type {
|
||||
|
@ -434,8 +435,9 @@ export const getDurationForUpdate = ({
|
|||
|
||||
export const getUserProfiles = async (
|
||||
securityStartPlugin: SecurityPluginStart,
|
||||
uids: Set<string>
|
||||
): Promise<Map<string, UserProfile>> => {
|
||||
uids: Set<string>,
|
||||
dataPath?: string
|
||||
): Promise<Map<string, UserProfileWithAvatar>> => {
|
||||
if (uids.size <= 0) {
|
||||
return new Map();
|
||||
}
|
||||
|
@ -443,9 +445,10 @@ export const getUserProfiles = async (
|
|||
const userProfiles =
|
||||
(await securityStartPlugin.userProfiles.bulkGet({
|
||||
uids,
|
||||
dataPath,
|
||||
})) ?? [];
|
||||
|
||||
return userProfiles.reduce<Map<string, UserProfile>>((acc, profile) => {
|
||||
return userProfiles.reduce<Map<string, UserProfileWithAvatar>>((acc, profile) => {
|
||||
acc.set(profile.uid, profile);
|
||||
return acc;
|
||||
}, new Map());
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { UserProfile } from '@kbn/security-plugin/common';
|
||||
import type { GetCaseUsersResponse, User } from '../../../common/api';
|
||||
import type { UserProfileWithAvatar } from '@kbn/user-profile-components';
|
||||
import type { GetCaseUsersResponse, User, UserWithProfileInfo } from '../../../common/api';
|
||||
import { GetCaseUsersResponseRt } from '../../../common/api';
|
||||
import type { OwnerEntity } from '../../authorization';
|
||||
import { Operations } from '../../authorization';
|
||||
|
@ -51,14 +51,17 @@ export const getUsers = async (
|
|||
.map((participant) => participant.user.profile_uid) as string[];
|
||||
|
||||
const assigneesUids = theCase.case.assignees.map((assignee) => assignee.uid);
|
||||
const reporter = theCase.case.created_by;
|
||||
const reporterProfileIdAsArray = reporter.profile_uid != null ? [reporter.profile_uid] : [];
|
||||
|
||||
const userProfileUids = new Set([
|
||||
...assignedAndUnassignedUsers,
|
||||
...participantsUids,
|
||||
...assigneesUids,
|
||||
...reporterProfileIdAsArray,
|
||||
]);
|
||||
|
||||
const userProfiles = await getUserProfiles(securityStartPlugin, userProfileUids);
|
||||
const userProfiles = await getUserProfiles(securityStartPlugin, userProfileUids, 'avatar');
|
||||
|
||||
const participantsResponse = convertUserInfoToResponse(
|
||||
userProfiles,
|
||||
|
@ -69,15 +72,19 @@ export const getUsers = async (
|
|||
);
|
||||
|
||||
const assigneesResponse = convertUserInfoToResponse(userProfiles, theCase.case.assignees);
|
||||
const reporterResponse = convertUserInfoToResponse(userProfiles, [
|
||||
{ uid: reporter.profile_uid, user: reporter },
|
||||
]);
|
||||
|
||||
/**
|
||||
* To avoid duplicates, a user that is
|
||||
* a participant or an assignee should not be
|
||||
* a participant or an assignee or a reporter should not be
|
||||
* part of the assignedAndUnassignedUsers Set
|
||||
*/
|
||||
const unassignedUsers = removeAllFromSet(assignedAndUnassignedUsers, [
|
||||
...participantsUids,
|
||||
...assigneesUids,
|
||||
...reporterProfileIdAsArray,
|
||||
]);
|
||||
|
||||
const unassignedUsersResponse = convertUserInfoToResponse(
|
||||
|
@ -91,6 +98,7 @@ export const getUsers = async (
|
|||
participants: participantsResponse,
|
||||
assignees: assigneesResponse,
|
||||
unassignedUsers: unassignedUsersResponse,
|
||||
reporter: reporterResponse[0],
|
||||
};
|
||||
|
||||
return GetCaseUsersResponseRt.encode(results);
|
||||
|
@ -104,9 +112,9 @@ export const getUsers = async (
|
|||
};
|
||||
|
||||
const convertUserInfoToResponse = (
|
||||
userProfiles: Map<string, UserProfile>,
|
||||
userProfiles: Map<string, UserProfileWithAvatar>,
|
||||
usersInfo: Array<{ uid: string | undefined; user?: User }>
|
||||
): User[] => {
|
||||
): UserWithProfileInfo[] => {
|
||||
const response = [];
|
||||
|
||||
for (const info of usersInfo) {
|
||||
|
@ -117,17 +125,20 @@ const convertUserInfoToResponse = (
|
|||
};
|
||||
|
||||
const getUserInformation = (
|
||||
userProfiles: Map<string, UserProfile>,
|
||||
userProfiles: Map<string, UserProfileWithAvatar>,
|
||||
uid: string | undefined,
|
||||
userInfo?: User
|
||||
): User => {
|
||||
): UserWithProfileInfo => {
|
||||
const userProfile = uid != null ? userProfiles.get(uid) : undefined;
|
||||
|
||||
return {
|
||||
email: userProfile?.user.email ?? userInfo?.email,
|
||||
full_name: userProfile?.user.full_name ?? userInfo?.full_name,
|
||||
username: userProfile?.user.username ?? userInfo?.username,
|
||||
profile_uid: userProfile?.uid ?? uid ?? userInfo?.profile_uid,
|
||||
user: {
|
||||
email: userProfile?.user.email ?? userInfo?.email ?? null,
|
||||
full_name: userProfile?.user.full_name ?? userInfo?.full_name ?? null,
|
||||
username: userProfile?.user.username ?? userInfo?.username ?? null,
|
||||
},
|
||||
avatar: userProfile?.data.avatar,
|
||||
uid: userProfile?.uid ?? uid ?? userInfo?.profile_uid,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -8,34 +8,38 @@
|
|||
import type SuperTest from 'supertest';
|
||||
import { parse as parseCookie, Cookie } from 'tough-cookie';
|
||||
|
||||
import { UserProfileBulkGetParams, UserProfileServiceStart } from '@kbn/security-plugin/server';
|
||||
import { INTERNAL_SUGGEST_USER_PROFILES_URL } from '@kbn/cases-plugin/common/constants';
|
||||
import { SuggestUserProfilesRequest } from '@kbn/cases-plugin/common/api';
|
||||
import { UserProfileService } from '@kbn/cases-plugin/server/services';
|
||||
import { UserProfileAvatarData } from '@kbn/security-plugin/common';
|
||||
import { UserProfile, UserProfileWithAvatar } from '@kbn/user-profile-components';
|
||||
import { superUser } from '../authentication/users';
|
||||
import { User } from '../authentication/types';
|
||||
import { getSpaceUrlPrefix } from './helpers';
|
||||
import { FtrProviderContext as CommonFtrProviderContext } from '../../ftr_provider_context';
|
||||
import { getUserInfo } from '../authentication';
|
||||
|
||||
type BulkGetUserProfilesParams = Omit<UserProfileBulkGetParams, 'uids'> & { uids: string[] };
|
||||
interface BulkGetUserProfilesParams<T> {
|
||||
uids: string[];
|
||||
dataPath?: T;
|
||||
}
|
||||
|
||||
export const generateFakeAssignees = (num: number) =>
|
||||
Array.from(Array(num).keys()).map((uid) => {
|
||||
return { uid: `${uid}` };
|
||||
});
|
||||
|
||||
export const bulkGetUserProfiles = async ({
|
||||
export const bulkGetUserProfiles = async <T extends string>({
|
||||
supertest,
|
||||
req,
|
||||
expectedHttpCode = 200,
|
||||
auth = { user: superUser, space: null },
|
||||
}: {
|
||||
supertest: SuperTest.SuperTest<SuperTest.Test>;
|
||||
req: BulkGetUserProfilesParams;
|
||||
req: BulkGetUserProfilesParams<T>;
|
||||
expectedHttpCode?: number;
|
||||
auth?: { user: User; space: string | null };
|
||||
}): ReturnType<UserProfileServiceStart['bulkGet']> => {
|
||||
}): Promise<Array<T extends 'avatar' ? UserProfileWithAvatar : UserProfile>> => {
|
||||
const { uids, ...restParams } = req;
|
||||
const uniqueIDs = [...new Set(uids)];
|
||||
|
||||
|
@ -70,6 +74,31 @@ export const suggestUserProfiles = async ({
|
|||
return profiles;
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the avatar of a user.
|
||||
* The API needs a valid user session.
|
||||
* The session acts as the user identifier
|
||||
* whose the avatar is updated.
|
||||
*/
|
||||
export const updateUserProfileAvatar = async ({
|
||||
supertest,
|
||||
req,
|
||||
expectedHttpCode = 200,
|
||||
headers = {},
|
||||
}: {
|
||||
supertest: SuperTest.SuperTest<SuperTest.Test>;
|
||||
req: UserProfileAvatarData;
|
||||
expectedHttpCode?: number;
|
||||
headers?: Record<string, unknown>;
|
||||
}): Promise<void> => {
|
||||
await supertest
|
||||
.post('/internal/security/user_profile/_data')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(headers)
|
||||
.send({ avatar: req })
|
||||
.expect(expectedHttpCode);
|
||||
};
|
||||
|
||||
export const loginUsers = async ({
|
||||
supertest,
|
||||
users = [superUser],
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import expect from '@kbn/expect';
|
||||
import { Cookie } from 'tough-cookie';
|
||||
import { UserProfile } from '@kbn/security-plugin/common';
|
||||
import { GetCaseUsersResponseRt } from '@kbn/cases-plugin/common/api';
|
||||
import { securitySolutionOnlyAllSpacesRole } from '../../../../common/lib/authentication/roles';
|
||||
import { getPostCaseRequest } from '../../../../common/lib/mock';
|
||||
import {
|
||||
|
@ -18,6 +19,7 @@ import {
|
|||
getCaseUsers,
|
||||
loginUsers,
|
||||
bulkGetUserProfiles,
|
||||
updateUserProfileAvatar,
|
||||
} from '../../../../common/lib/api';
|
||||
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||
import { createUsersAndRoles, deleteUsersAndRoles } from '../../../../common/lib/authentication';
|
||||
|
@ -56,12 +58,15 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
title: 'new title',
|
||||
});
|
||||
|
||||
const { participants, assignees, unassignedUsers } = await getCaseUsers({
|
||||
const { participants, assignees, unassignedUsers, reporter } = await getCaseUsers({
|
||||
caseId: postedCase.id,
|
||||
supertest,
|
||||
});
|
||||
|
||||
expect(participants).to.eql([{ username: 'elastic', full_name: null, email: null }]);
|
||||
expect(participants).to.eql([
|
||||
{ user: { username: 'elastic', full_name: null, email: null } },
|
||||
]);
|
||||
expect(reporter).to.eql({ user: { username: 'elastic', full_name: null, email: null } });
|
||||
expect(assignees).to.eql([]);
|
||||
expect(unassignedUsers).to.eql([]);
|
||||
});
|
||||
|
@ -91,14 +96,19 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
caseId: '163d5820-1284-21ed-81af-63a2bdfb2bf9',
|
||||
});
|
||||
|
||||
const { participants, assignees, unassignedUsers } = await getCaseUsers({
|
||||
const { participants, assignees, unassignedUsers, reporter } = await getCaseUsers({
|
||||
caseId: theCase.id,
|
||||
supertest,
|
||||
});
|
||||
|
||||
expect(participants).to.eql([{ username: null, full_name: null, email: null }]);
|
||||
expect(assignees).to.eql([{ profile_uid: 'abc' }]);
|
||||
expect(unassignedUsers).to.eql([{ profile_uid: 'dfg' }]);
|
||||
expect(participants).to.eql([{ user: { username: null, full_name: null, email: null } }]);
|
||||
expect(reporter).to.eql({ user: { username: null, full_name: null, email: null } });
|
||||
expect(assignees).to.eql([
|
||||
{ uid: 'abc', user: { username: null, full_name: null, email: null } },
|
||||
]);
|
||||
expect(unassignedUsers).to.eql([
|
||||
{ uid: 'dfg', user: { username: null, full_name: null, email: null } },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -141,10 +151,27 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
createCase(supertestWithoutAuth, getPostCaseRequest(), 200, null, secOnlyHeaders),
|
||||
]);
|
||||
|
||||
/**
|
||||
* Update superUser profile avatar.
|
||||
* Need for schema and response verification
|
||||
*/
|
||||
await updateUserProfileAvatar({
|
||||
supertest,
|
||||
req: {
|
||||
initials: 'ES',
|
||||
color: '#6092C0',
|
||||
imageUrl: 'my-image',
|
||||
},
|
||||
headers: superUserHeaders,
|
||||
});
|
||||
|
||||
const userProfiles = await bulkGetUserProfiles({
|
||||
supertest,
|
||||
// @ts-expect-error: profile uids are defined for both users
|
||||
req: { uids: [superUserCase.created_by.profile_uid, secUserCase.created_by.profile_uid] },
|
||||
req: {
|
||||
// @ts-expect-error: profile uids are defined for both users
|
||||
uids: [superUserCase.created_by.profile_uid, secUserCase.created_by.profile_uid],
|
||||
dataPath: 'avatar',
|
||||
},
|
||||
});
|
||||
|
||||
superUserProfile = userProfiles[0];
|
||||
|
@ -159,156 +186,251 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
);
|
||||
});
|
||||
|
||||
it('returns only the creator of the case', async () => {
|
||||
const postedCase = await createCase(
|
||||
supertestWithoutAuth,
|
||||
getPostCaseRequest(),
|
||||
200,
|
||||
null,
|
||||
superUserHeaders
|
||||
);
|
||||
describe('schema', () => {
|
||||
it('ensures that the schema of security plugin is as expected', async () => {
|
||||
const res = await bulkGetUserProfiles({
|
||||
supertest: supertestWithoutAuth,
|
||||
req: {
|
||||
uids: [superUserProfile.uid],
|
||||
dataPath: 'avatar',
|
||||
},
|
||||
});
|
||||
|
||||
const { participants, assignees, unassignedUsers } = await getCaseUsers({
|
||||
caseId: postedCase.id,
|
||||
supertest,
|
||||
const userProfile = res[0];
|
||||
|
||||
try {
|
||||
const userToValidate = {
|
||||
user: {
|
||||
email: userProfile.user.email ?? null,
|
||||
full_name: userProfile.user.full_name ?? null,
|
||||
username: userProfile.user.username ?? null,
|
||||
},
|
||||
avatar: userProfile.data.avatar,
|
||||
uid: userProfile.uid,
|
||||
};
|
||||
|
||||
GetCaseUsersResponseRt.encode({
|
||||
assignees: [userToValidate],
|
||||
unassignedUsers: [userToValidate],
|
||||
participants: [userToValidate],
|
||||
reporter: userToValidate,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed encoding GetCaseUsersResponse schema. Schema mismatch between Case users and Security user profiles. Error message: ${error}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
expect(participants).to.eql([
|
||||
{
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
profile_uid: superUserProfile.uid,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(assignees).to.eql([]);
|
||||
expect(unassignedUsers).to.eql([]);
|
||||
});
|
||||
|
||||
it('returns one participant if it is the only one that participates to the case', async () => {
|
||||
const postedCase = await createCase(
|
||||
supertestWithoutAuth,
|
||||
getPostCaseRequest(),
|
||||
200,
|
||||
null,
|
||||
superUserHeaders
|
||||
);
|
||||
describe('case users', () => {
|
||||
it('returns only the creator of the case', async () => {
|
||||
const postedCase = await createCase(
|
||||
supertestWithoutAuth,
|
||||
getPostCaseRequest(),
|
||||
200,
|
||||
null,
|
||||
superUserHeaders
|
||||
);
|
||||
|
||||
await changeCaseTitle({
|
||||
supertest,
|
||||
caseId: postedCase.id,
|
||||
version: postedCase.version,
|
||||
title: 'new title',
|
||||
headers: superUserHeaders,
|
||||
const { participants, assignees, unassignedUsers, reporter } = await getCaseUsers({
|
||||
caseId: postedCase.id,
|
||||
supertest,
|
||||
});
|
||||
|
||||
expect(participants).to.eql([
|
||||
{
|
||||
user: {
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
},
|
||||
avatar: superUserProfile.data.avatar,
|
||||
uid: superUserProfile.uid,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(reporter).to.eql({
|
||||
user: {
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
},
|
||||
avatar: superUserProfile.data.avatar,
|
||||
uid: superUserProfile.uid,
|
||||
});
|
||||
|
||||
expect(assignees).to.eql([]);
|
||||
expect(unassignedUsers).to.eql([]);
|
||||
});
|
||||
|
||||
const { participants, assignees, unassignedUsers } = await getCaseUsers({
|
||||
caseId: postedCase.id,
|
||||
supertest,
|
||||
it('returns one participant if it is the only one that participates to the case', async () => {
|
||||
const postedCase = await createCase(
|
||||
supertestWithoutAuth,
|
||||
getPostCaseRequest(),
|
||||
200,
|
||||
null,
|
||||
superUserHeaders
|
||||
);
|
||||
|
||||
await changeCaseTitle({
|
||||
supertest,
|
||||
caseId: postedCase.id,
|
||||
version: postedCase.version,
|
||||
title: 'new title',
|
||||
headers: superUserHeaders,
|
||||
});
|
||||
|
||||
const { participants, assignees, unassignedUsers, reporter } = await getCaseUsers({
|
||||
caseId: postedCase.id,
|
||||
supertest,
|
||||
});
|
||||
|
||||
expect(participants).to.eql([
|
||||
{
|
||||
user: {
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
},
|
||||
avatar: superUserProfile.data.avatar,
|
||||
uid: superUserProfile.uid,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(reporter).to.eql({
|
||||
user: {
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
},
|
||||
avatar: superUserProfile.data.avatar,
|
||||
uid: superUserProfile.uid,
|
||||
});
|
||||
|
||||
expect(assignees).to.eql([]);
|
||||
expect(unassignedUsers).to.eql([]);
|
||||
});
|
||||
|
||||
expect(participants).to.eql([
|
||||
{
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
profile_uid: superUserProfile.uid,
|
||||
},
|
||||
]);
|
||||
it('returns all participants of the case', async () => {
|
||||
const postedCase = await createCase(
|
||||
supertestWithoutAuth,
|
||||
getPostCaseRequest(),
|
||||
200,
|
||||
null,
|
||||
superUserHeaders
|
||||
);
|
||||
|
||||
expect(assignees).to.eql([]);
|
||||
expect(unassignedUsers).to.eql([]);
|
||||
});
|
||||
await changeCaseTitle({
|
||||
supertest,
|
||||
caseId: postedCase.id,
|
||||
version: postedCase.version,
|
||||
title: 'new title',
|
||||
headers: secOnlyHeaders,
|
||||
});
|
||||
|
||||
it('returns all participants of the case', async () => {
|
||||
const postedCase = await createCase(
|
||||
supertestWithoutAuth,
|
||||
getPostCaseRequest(),
|
||||
200,
|
||||
null,
|
||||
superUserHeaders
|
||||
);
|
||||
const { participants, assignees, unassignedUsers, reporter } = await getCaseUsers({
|
||||
caseId: postedCase.id,
|
||||
supertest,
|
||||
});
|
||||
|
||||
await changeCaseTitle({
|
||||
supertest,
|
||||
caseId: postedCase.id,
|
||||
version: postedCase.version,
|
||||
title: 'new title',
|
||||
headers: secOnlyHeaders,
|
||||
expect(participants).to.eql([
|
||||
{
|
||||
user: {
|
||||
username: secUserProfile.user.username,
|
||||
full_name: secUserProfile.user.full_name,
|
||||
email: secUserProfile.user.email,
|
||||
},
|
||||
uid: secUserProfile.uid,
|
||||
},
|
||||
{
|
||||
user: {
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
},
|
||||
avatar: superUserProfile.data.avatar,
|
||||
uid: superUserProfile.uid,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(reporter).to.eql({
|
||||
user: {
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
},
|
||||
avatar: superUserProfile.data.avatar,
|
||||
uid: superUserProfile.uid,
|
||||
});
|
||||
|
||||
expect(assignees).to.eql([]);
|
||||
expect(unassignedUsers).to.eql([]);
|
||||
});
|
||||
|
||||
const { participants, assignees, unassignedUsers } = await getCaseUsers({
|
||||
caseId: postedCase.id,
|
||||
supertest,
|
||||
it('does not return duplicate participants', async () => {
|
||||
const postedCase = await createCase(
|
||||
supertestWithoutAuth,
|
||||
getPostCaseRequest(),
|
||||
200,
|
||||
null,
|
||||
superUserHeaders
|
||||
);
|
||||
|
||||
const updatedCases = await changeCaseTitle({
|
||||
supertest,
|
||||
caseId: postedCase.id,
|
||||
version: postedCase.version,
|
||||
title: 'new title',
|
||||
headers: secOnlyHeaders,
|
||||
});
|
||||
|
||||
await changeCaseDescription({
|
||||
supertest,
|
||||
caseId: updatedCases[0].id,
|
||||
version: updatedCases[0].version,
|
||||
description: 'new desc',
|
||||
headers: secOnlyHeaders,
|
||||
});
|
||||
|
||||
const { participants, assignees, unassignedUsers, reporter } = await getCaseUsers({
|
||||
caseId: postedCase.id,
|
||||
supertest,
|
||||
});
|
||||
|
||||
expect(participants).to.eql([
|
||||
{
|
||||
user: {
|
||||
username: secUserProfile.user.username,
|
||||
full_name: secUserProfile.user.full_name,
|
||||
email: secUserProfile.user.email,
|
||||
},
|
||||
uid: secUserProfile.uid,
|
||||
},
|
||||
{
|
||||
user: {
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
},
|
||||
avatar: superUserProfile.data.avatar,
|
||||
uid: superUserProfile.uid,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(reporter).to.eql({
|
||||
user: {
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
},
|
||||
avatar: superUserProfile.data.avatar,
|
||||
uid: superUserProfile.uid,
|
||||
});
|
||||
|
||||
expect(assignees).to.eql([]);
|
||||
expect(unassignedUsers).to.eql([]);
|
||||
});
|
||||
|
||||
expect(participants).to.eql([
|
||||
{
|
||||
username: secUserProfile.user.username,
|
||||
full_name: secUserProfile.user.full_name,
|
||||
email: secUserProfile.user.email,
|
||||
profile_uid: secUserProfile.uid,
|
||||
},
|
||||
{
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
profile_uid: superUserProfile.uid,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(assignees).to.eql([]);
|
||||
expect(unassignedUsers).to.eql([]);
|
||||
});
|
||||
|
||||
it('does not return duplicate participants', async () => {
|
||||
const postedCase = await createCase(
|
||||
supertestWithoutAuth,
|
||||
getPostCaseRequest(),
|
||||
200,
|
||||
null,
|
||||
superUserHeaders
|
||||
);
|
||||
|
||||
const updatedCases = await changeCaseTitle({
|
||||
supertest,
|
||||
caseId: postedCase.id,
|
||||
version: postedCase.version,
|
||||
title: 'new title',
|
||||
headers: secOnlyHeaders,
|
||||
});
|
||||
|
||||
await changeCaseDescription({
|
||||
supertest,
|
||||
caseId: updatedCases[0].id,
|
||||
version: updatedCases[0].version,
|
||||
description: 'new desc',
|
||||
headers: secOnlyHeaders,
|
||||
});
|
||||
|
||||
const { participants, assignees, unassignedUsers } = await getCaseUsers({
|
||||
caseId: postedCase.id,
|
||||
supertest,
|
||||
});
|
||||
|
||||
expect(participants).to.eql([
|
||||
{
|
||||
username: secUserProfile.user.username,
|
||||
full_name: secUserProfile.user.full_name,
|
||||
email: secUserProfile.user.email,
|
||||
profile_uid: secUserProfile.uid,
|
||||
},
|
||||
{
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
profile_uid: superUserProfile.uid,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(assignees).to.eql([]);
|
||||
expect(unassignedUsers).to.eql([]);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -108,32 +108,47 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
headers: superUserHeaders,
|
||||
});
|
||||
|
||||
const { participants, assignees, unassignedUsers } = await getCaseUsers({
|
||||
const { participants, assignees, unassignedUsers, reporter } = await getCaseUsers({
|
||||
caseId: postedCase.id,
|
||||
supertest,
|
||||
});
|
||||
|
||||
expect(participants).to.eql([
|
||||
{
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
profile_uid: superUserProfile.uid,
|
||||
user: {
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
},
|
||||
uid: superUserProfile.uid,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(assignees).to.eql([
|
||||
{
|
||||
expect(reporter).to.eql({
|
||||
user: {
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
profile_uid: superUserProfile.uid,
|
||||
},
|
||||
uid: superUserProfile.uid,
|
||||
});
|
||||
|
||||
expect(assignees).to.eql([
|
||||
{
|
||||
user: {
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
},
|
||||
uid: superUserProfile.uid,
|
||||
},
|
||||
{
|
||||
username: secUserProfile.user.username,
|
||||
full_name: secUserProfile.user.full_name,
|
||||
email: secUserProfile.user.email,
|
||||
profile_uid: secUserProfile.uid,
|
||||
user: {
|
||||
username: secUserProfile.user.username,
|
||||
full_name: secUserProfile.user.full_name,
|
||||
email: secUserProfile.user.email,
|
||||
},
|
||||
uid: secUserProfile.uid,
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -167,35 +182,50 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
headers: superUserHeaders,
|
||||
});
|
||||
|
||||
const { participants, assignees, unassignedUsers } = await getCaseUsers({
|
||||
const { participants, assignees, unassignedUsers, reporter } = await getCaseUsers({
|
||||
caseId: postedCase.id,
|
||||
supertest,
|
||||
});
|
||||
|
||||
expect(participants).to.eql([
|
||||
{
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
profile_uid: superUserProfile.uid,
|
||||
user: {
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
},
|
||||
uid: superUserProfile.uid,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(assignees).to.eql([
|
||||
{
|
||||
expect(reporter).to.eql({
|
||||
user: {
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
profile_uid: superUserProfile.uid,
|
||||
},
|
||||
uid: superUserProfile.uid,
|
||||
});
|
||||
|
||||
expect(assignees).to.eql([
|
||||
{
|
||||
user: {
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
},
|
||||
uid: superUserProfile.uid,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(unassignedUsers).to.eql([
|
||||
{
|
||||
username: secUserProfile.user.username,
|
||||
full_name: secUserProfile.user.full_name,
|
||||
email: secUserProfile.user.email,
|
||||
profile_uid: secUserProfile.uid,
|
||||
user: {
|
||||
username: secUserProfile.user.username,
|
||||
full_name: secUserProfile.user.full_name,
|
||||
email: secUserProfile.user.email,
|
||||
},
|
||||
uid: secUserProfile.uid,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -236,32 +266,47 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
headers: superUserHeaders,
|
||||
});
|
||||
|
||||
const { participants, assignees, unassignedUsers } = await getCaseUsers({
|
||||
const { participants, assignees, unassignedUsers, reporter } = await getCaseUsers({
|
||||
caseId: postedCase.id,
|
||||
supertest,
|
||||
});
|
||||
|
||||
expect(participants).to.eql([
|
||||
{
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
profile_uid: superUserProfile.uid,
|
||||
user: {
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
},
|
||||
uid: superUserProfile.uid,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(assignees).to.eql([
|
||||
{
|
||||
expect(reporter).to.eql({
|
||||
user: {
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
profile_uid: superUserProfile.uid,
|
||||
},
|
||||
uid: superUserProfile.uid,
|
||||
});
|
||||
|
||||
expect(assignees).to.eql([
|
||||
{
|
||||
user: {
|
||||
username: superUserProfile.user.username,
|
||||
full_name: superUserProfile.user.full_name,
|
||||
email: superUserProfile.user.email,
|
||||
},
|
||||
uid: superUserProfile.uid,
|
||||
},
|
||||
{
|
||||
username: secUserProfile.user.username,
|
||||
full_name: secUserProfile.user.full_name,
|
||||
email: secUserProfile.user.email,
|
||||
profile_uid: secUserProfile.uid,
|
||||
user: {
|
||||
username: secUserProfile.user.username,
|
||||
full_name: secUserProfile.user.full_name,
|
||||
email: secUserProfile.user.email,
|
||||
},
|
||||
uid: secUserProfile.uid,
|
||||
},
|
||||
]);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue