import { SearchRulesQuery } from '../../types/rules';
import { Standards } from '../../types/security';
import { Dict, Rule, RuleActivation, RuleDetails, SnippetsByComponent } from '../../types/types';
-import { LoggedInUser, NoticeType, RestUser } from '../../types/users';
import {
addIssueComment,
bulkChangeIssues,
getIssueFlowSnippets,
listIssues,
searchIssueAuthors,
- searchIssueTags,
searchIssues,
+ searchIssueTags,
setIssueAssignee,
setIssueSeverity,
setIssueTags,
setIssueType,
} from '../issues';
import { getRuleDetails, searchRules } from '../rules';
-import { dismissNotice, getCurrentUser, getUsers } from '../users';
import { IssueData, mockIssuesList } from './data/issues';
import { mockRuleList } from './data/rules';
+import UsersServiceMock from './UsersServiceMock';
jest.mock('../../api/issues');
// The following 2 mocks are needed, because IssuesServiceMock mocks more than it should.
// This should be removed once IssuesServiceMock is cleaned up.
jest.mock('../../api/rules');
-jest.mock('../../api/users');
function mockReferenceComponent(override?: Partial<ReferencedComponent>) {
return {
export default class IssuesServiceMock {
isAdmin = false;
- currentUser: LoggedInUser;
standards?: Standards;
+ usersServiceMock?: UsersServiceMock;
defaultList: IssueData[];
rulesList: Rule[];
list: IssueData[];
- constructor() {
- this.currentUser = mockLoggedInUser();
+ constructor(usersServiceMock?: UsersServiceMock) {
+ this.usersServiceMock = usersServiceMock;
this.defaultList = mockIssuesList();
this.rulesList = mockRuleList();
jest.mocked(addIssueComment).mockImplementation(this.handleAddComment);
jest.mocked(bulkChangeIssues).mockImplementation(this.handleBulkChangeIssues);
jest.mocked(deleteIssueComment).mockImplementation(this.handleDeleteComment);
- jest.mocked(dismissNotice).mockImplementation(this.handleDismissNotification);
jest.mocked(editIssueComment).mockImplementation(this.handleEditComment);
- jest.mocked(getCurrentUser).mockImplementation(this.handleGetCurrentUser);
jest.mocked(getIssueChangelog).mockImplementation(this.handleGetIssueChangelog);
jest.mocked(getIssueFlowSnippets).mockImplementation(this.handleGetIssueFlowSnippets);
jest.mocked(getRuleDetails).mockImplementation(this.handleGetRuleDetails);
jest.mocked(searchIssues).mockImplementation(this.handleSearchIssues);
jest.mocked(searchIssueTags).mockImplementation(this.handleSearchIssueTags);
jest.mocked(searchRules).mockImplementation(this.handleSearchRules);
- jest.mocked(getUsers).mockImplementation(this.handleGetUsers);
jest.mocked(setIssueAssignee).mockImplementation(this.handleSetIssueAssignee);
jest.mocked(setIssueSeverity).mockImplementation(this.handleSetIssueSeverity);
jest.mocked(setIssueTags).mockImplementation(this.handleSetIssueTags);
reset = () => {
this.list = cloneDeep(this.defaultList);
- this.currentUser = mockLoggedInUser();
- };
-
- setCurrentUser = (user: LoggedInUser) => {
- this.currentUser = user;
};
setIssueList = (list: IssueData[]) => {
});
};
- handleGetCurrentUser = () => {
- return this.reply(this.currentUser);
- };
-
- handleDismissNotification = (noticeType: NoticeType) => {
- if (
- [
- NoticeType.EDUCATION_PRINCIPLES,
- NoticeType.ISSUE_GUIDE,
- NoticeType.ISSUE_NEW_STATUS_AND_TRANSITION_GUIDE,
- ].includes(noticeType)
- ) {
- return this.reply(true);
- }
-
- return Promise.reject();
- };
-
handleSetIssueType = (data: { issue: string; type: IssueType }) => {
return this.getActionsResponse({ type: data.type }, data.issue);
};
handleSetIssueAssignee = (data: { issue: string; assignee?: string }) => {
return this.getActionsResponse(
- { assignee: data.assignee === '_me' ? this.currentUser.login : data.assignee },
+ {
+ assignee:
+ data.assignee === '_me' ? this.usersServiceMock?.currentUser.login : data.assignee,
+ },
data.issue,
);
};
);
};
- handleGetUsers = () => {
- return this.reply({
- page: mockPaging(),
- users: [mockLoggedInUser() as unknown as RestUser],
- });
- };
-
handleSearchIssueAuthors = () => {
return this.reply(mockIssueAuthors());
};
import { isAfter, isBefore } from 'date-fns';
import { cloneDeep, isEmpty, isUndefined, omitBy } from 'lodash';
import { HttpStatus } from '../../helpers/request';
-import { mockIdentityProvider, mockRestUser } from '../../helpers/testMocks';
+import { mockIdentityProvider, mockLoggedInUser, mockRestUser } from '../../helpers/testMocks';
import { IdentityProvider } from '../../types/types';
-import { ChangePasswordResults, RestUserDetailed } from '../../types/users';
+import {
+ ChangePasswordResults,
+ LoggedInUser,
+ NoticeType,
+ RestUserDetailed,
+} from '../../types/users';
import { addUserToGroup, removeUserFromGroup } from '../legacy-group-membership';
import {
UserGroup,
changePassword,
deleteUser,
dismissNotice,
+ getCurrentUser,
getIdentityProviders,
getUserGroups,
getUsers,
export default class UsersServiceMock {
isManaged = true;
users = cloneDeep(DEFAULT_USERS);
+ currentUser = mockLoggedInUser();
groups = cloneDeep(DEFAULT_GROUPS);
password = DEFAULT_PASSWORD;
groupMembershipsServiceMock?: GroupMembershipsServiceMock = undefined;
jest.mocked(removeUserFromGroup).mockImplementation(this.handleRemoveUserFromGroup);
jest.mocked(changePassword).mockImplementation(this.handleChangePassword);
jest.mocked(deleteUser).mockImplementation(this.handleDeactivateUser);
- jest.mocked(dismissNotice).mockResolvedValue({});
+ jest.mocked(dismissNotice).mockImplementation(this.handleDismissNotification);
+ jest.mocked(getCurrentUser).mockImplementation(this.handleGetCurrentUser);
}
getFilteredRestUsers = (filterParams: Parameters<typeof getUsers>[0]) => {
return false;
}
- if (q && (!user.login.includes(q) || (user.name && !user.name.includes(q)))) {
+ if (q && !user.login.includes(q) && !user.name?.includes(q) && !user.email?.includes(q)) {
return false;
}
return this.reply(undefined);
};
+ handleDismissNotification: typeof dismissNotice = (noticeType: NoticeType) => {
+ if (Object.values(NoticeType).includes(noticeType)) {
+ return this.reply(true);
+ }
+
+ return Promise.reject();
+ };
+
+ setCurrentUser = (user: LoggedInUser) => {
+ this.currentUser = user;
+ };
+
+ handleGetCurrentUser: typeof getCurrentUser = () => {
+ return this.reply(this.currentUser);
+ };
+
reset = () => {
this.isManaged = true;
this.users = cloneDeep(DEFAULT_USERS);
this.groups = cloneDeep(DEFAULT_GROUPS);
this.password = DEFAULT_PASSWORD;
+ this.currentUser = mockLoggedInUser();
};
reply<T>(response: T): Promise<T> {
status: IssueDeprecatedStatus.Open,
issueStatus: IssueStatus.Open,
ruleDescriptionContextKey: 'spring',
+ author: 'bob.marley@test.com',
}),
snippets: keyBy(
[
resolution: IssueResolution.Fixed,
status: IssueDeprecatedStatus.Confirmed,
issueStatus: IssueStatus.Confirmed,
+ author: 'unknownemail@test.com',
}),
snippets: keyBy(
[
import userEvent from '@testing-library/user-event';
import React from 'react';
import { TabKeys } from '../../../components/rules/RuleTabViewer';
+import { mockLoggedInUser } from '../../../helpers/testMocks';
import { byRole } from '../../../helpers/testSelector';
+import { RestUserDetailed } from '../../../types/users';
import {
branchHandler,
componentsHandler,
renderIssueApp,
renderProjectIssuesApp,
ui,
+ usersHandler,
} from '../test-utils';
jest.mock('../sidebar/Sidebar', () => {
issuesHandler.reset();
componentsHandler.reset();
branchHandler.reset();
+ usersHandler.reset();
+ usersHandler.users = [mockLoggedInUser() as unknown as RestUserDetailed];
window.scrollTo = jest.fn();
window.HTMLElement.prototype.scrollTo = jest.fn();
});
renderIssueApp,
renderProjectIssuesApp,
ui,
+ usersHandler,
waitOnDataLoaded,
} from '../test-utils';
issuesHandler.reset();
componentsHandler.reset();
branchHandler.reset();
+ usersHandler.reset();
window.scrollTo = jest.fn();
window.HTMLElement.prototype.scrollTo = jest.fn();
});
it('should allow to set creation date', async () => {
const user = userEvent.setup();
const currentUser = mockLoggedInUser({ dismissedNotices: { [NoticeType.ISSUE_GUIDE]: true } });
- issuesHandler.setCurrentUser(currentUser);
+ usersHandler.setCurrentUser(currentUser);
renderIssueApp(currentUser);
it('should allow to only show my issues', async () => {
const user = userEvent.setup();
const currentUser = mockLoggedInUser({ dismissedNotices: { [NoticeType.ISSUE_GUIDE]: true } });
- issuesHandler.setCurrentUser(currentUser);
+ usersHandler.setCurrentUser(currentUser);
renderIssueApp(currentUser);
await waitOnDataLoaded();
renderIssueApp,
renderProjectIssuesApp,
ui,
+ usersHandler,
} from '../test-utils';
jest.mock('../sidebar/Sidebar', () => {
issuesHandler.reset();
componentsHandler.reset();
branchHandler.reset();
+ usersHandler.reset();
window.scrollTo = jest.fn();
window.HTMLElement.prototype.scrollTo = jest.fn();
});
dismissedNotices: { [NoticeType.ISSUE_GUIDE]: true },
});
issuesHandler.setIsAdmin(true);
- issuesHandler.setCurrentUser(currentUser);
+ usersHandler.setCurrentUser(currentUser);
renderIssueApp(currentUser);
// Check that the bulk button has correct behavior
dismissedNotices: { [NoticeType.ISSUE_GUIDE]: true },
});
issuesHandler.setIsAdmin(true);
- issuesHandler.setCurrentUser(currentUser);
+ usersHandler.setCurrentUser(currentUser);
renderIssueApp(currentUser);
// Check that we bulk change the selected issue
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
-import { branchHandler, componentsHandler, issuesHandler, renderIssueApp, ui } from '../test-utils';
+import { mockRestUser } from '../../../helpers/testMocks';
+import {
+ branchHandler,
+ componentsHandler,
+ issuesHandler,
+ renderIssueApp,
+ ui,
+ usersHandler,
+} from '../test-utils';
jest.mock('../sidebar/Sidebar', () => {
const fakeSidebar = () => {
issuesHandler.reset();
componentsHandler.reset();
branchHandler.reset();
+ usersHandler.reset();
+ usersHandler.users = [
+ mockRestUser({
+ login: 'bob.marley',
+ email: 'bob.marley@test.com',
+ name: 'Bob Marley',
+ }),
+ ];
window.scrollTo = jest.fn();
window.HTMLElement.prototype.scrollTo = jest.fn();
});
await user.click(ui.issueActivityTab.get());
- expect(screen.getByText('issue.activity.review_history.created')).toBeInTheDocument();
+ expect(screen.getByText(/issue.activity.review_history.created/)).toHaveTextContent('Bob Marley');
expect(
screen.getByText(
'issue.changelog.changed_to.issue.changelog.field.assign.darth.vader (issue.changelog.was.luke.skywalker)',
),
).not.toBeInTheDocument();
});
+
+it('should show author email if there is no user with that email', async () => {
+ const user = userEvent.setup();
+ issuesHandler.setIsAdmin(true);
+ renderIssueApp();
+
+ await user.click(await ui.issueItemAction6.find());
+
+ await user.click(ui.issueActivityTab.get());
+
+ expect(screen.getByText(/issue.activity.review_history.created/)).toHaveTextContent(
+ 'unknownemail@test.com',
+ );
+});
renderIssueApp,
renderProjectIssuesApp,
ui,
+ usersHandler,
} from '../test-utils';
jest.mock('../sidebar/Sidebar', () => {
issuesHandler.reset();
componentsHandler.reset();
branchHandler.reset();
+ usersHandler.reset();
window.scrollTo = jest.fn();
window.HTMLElement.prototype.scrollTo = jest.fn();
});
import { act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
-import IssuesServiceMock from '../../../api/mocks/IssuesServiceMock';
import CurrentUserContextProvider from '../../../app/components/current-user/CurrentUserContextProvider';
import IssueTransitionComponent from '../../../components/issue/components/IssueTransition';
import { mockCurrentUser, mockIssue } from '../../../helpers/testMocks';
import { Issue } from '../../../types/types';
import { NoticeType } from '../../../types/users';
import IssueNewStatusAndTransitionGuide from '../components/IssueNewStatusAndTransitionGuide';
-import { ui } from '../test-utils';
-
-const issuesHandler = new IssuesServiceMock();
+import { issuesHandler, ui } from '../test-utils';
beforeEach(() => {
issuesHandler.reset();
componentsHandler,
issuesHandler,
renderProjectIssuesApp,
+ usersHandler,
waitOnDataLoaded,
} from '../test-utils';
beforeEach(() => {
issuesHandler.reset();
componentsHandler.reset();
+ usersHandler.reset();
window.scrollTo = jest.fn();
window.HTMLElement.prototype.scrollTo = jest.fn();
});
import { ReviewHistoryType } from '../../../types/security-hotspots';
import { Issue, IssueChangelog } from '../../../types/types';
import HotspotCommentModal from '../../security-hotspots/components/HotspotCommentModal';
-import { getIssueReviewHistory } from '../crossComponentSourceViewer/utils';
+import { useGetIssueReviewHistory } from '../crossComponentSourceViewer/utils';
export interface HotspotReviewHistoryProps {
issue: Issue;
export default function IssueReviewHistory(props: HotspotReviewHistoryProps) {
const { issue } = props;
const [changeLog, setChangeLog] = React.useState<IssueChangelog[]>([]);
- const history = getIssueReviewHistory(issue, changeLog);
+ const history = useGetIssueReviewHistory(issue, changeLog);
const [editCommentKey, setEditCommentKey] = React.useState('');
const [deleteCommentKey, setDeleteCommentKey] = React.useState('');
import { sortBy } from 'lodash';
import { decorateWithUnderlineFlags } from '../../../helpers/code-viewer';
import { isDefined } from '../../../helpers/types';
+import { useUsersQueries } from '../../../queries/users';
import { ComponentQualifier } from '../../../types/component';
import { ReviewHistoryElement, ReviewHistoryType } from '../../../types/security-hotspots';
import {
SnippetsByComponent,
SourceLine,
} from '../../../types/types';
+import { RestUser } from '../../../types/users';
const LINES_ABOVE = 5;
const LINES_BELOW = 5;
return line >= snippet[0].line && line <= snippet[snippet.length - 1].line;
}
-export function getIssueReviewHistory(
+export function useGetIssueReviewHistory(
issue: Issue,
changelog: IssueChangelog[],
): ReviewHistoryElement[] {
const history: ReviewHistoryElement[] = [];
+ const { data } = useUsersQueries<RestUser>({ q: issue.author ?? '' }, !!issue.author);
+ const author = data?.pages[0]?.users[0] ?? null;
+
if (issue.creationDate) {
history.push({
type: ReviewHistoryType.Creation,
date: issue.creationDate,
user: {
- active: issue.assigneeActive,
- avatar: issue.assigneeAvatar,
- name: issue.assigneeName || issue.assigneeLogin,
+ active: true,
+ avatar: author?.avatar,
+ name: author?.name ?? author?.login ?? issue.author,
},
});
}
import BranchesServiceMock from '../../api/mocks/BranchesServiceMock';
import ComponentsServiceMock from '../../api/mocks/ComponentsServiceMock';
import IssuesServiceMock from '../../api/mocks/IssuesServiceMock';
+import UsersServiceMock from '../../api/mocks/UsersServiceMock';
import { mockComponent } from '../../helpers/mocks/component';
import { mockCurrentUser } from '../../helpers/testMocks';
import { renderApp, renderAppWithComponentContext } from '../../helpers/testReactTestingUtils';
import IssuesApp from './components/IssuesApp';
import { projectIssuesRoutes } from './routes';
-export const issuesHandler = new IssuesServiceMock();
+export const usersHandler = new UsersServiceMock();
+export const issuesHandler = new IssuesServiceMock(usersHandler);
export const componentsHandler = new ComponentsServiceMock();
export const branchHandler = new BranchesServiceMock();
import * as React from 'react';
import ComponentsServiceMock from '../../../api/mocks/ComponentsServiceMock';
import IssuesServiceMock from '../../../api/mocks/IssuesServiceMock';
+import UsersServiceMock from '../../../api/mocks/UsersServiceMock';
import { HttpStatus } from '../../../helpers/request';
-import { mockIssue } from '../../../helpers/testMocks';
+import { mockIssue, mockLoggedInUser } from '../../../helpers/testMocks';
import { renderComponent } from '../../../helpers/testReactTestingUtils';
+import { RestUserDetailed } from '../../../types/users';
import SourceViewer, { Props } from '../SourceViewer';
import loadIssues from '../helpers/loadIssues';
// The following 2 mocks are needed, because IssuesServiceMock mocks more than it should.
// This should be removed once IssuesServiceMock is cleaned up.
jest.mock('../../../api/rules');
-jest.mock('../../../api/users');
jest.mock('../helpers/loadIssues', () => ({
__esModule: true,
const componentsHandler = new ComponentsServiceMock();
const issuesHandler = new IssuesServiceMock();
+const usersHandler = new UsersServiceMock();
const message = 'First Issue';
beforeEach(() => {
issuesHandler.reset();
componentsHandler.reset();
+ usersHandler.reset();
+ usersHandler.users = [mockLoggedInUser() as unknown as RestUserDetailed];
});
it('should show a permalink on line number', async () => {
import * as React from 'react';
import { Route } from 'react-router-dom';
import IssuesServiceMock from '../../../api/mocks/IssuesServiceMock';
+import UsersServiceMock from '../../../api/mocks/UsersServiceMock';
import { KeyboardKeys } from '../../../helpers/keycodes';
import { mockIssue, mockLoggedInUser, mockRawIssue } from '../../../helpers/testMocks';
import { renderAppRoutes } from '../../../helpers/testReactTestingUtils';
IssueTransition,
IssueType,
} from '../../../types/issues';
+import { RestUserDetailed } from '../../../types/users';
import Issue from '../Issue';
jest.mock('../../../helpers/preferences', () => ({
getKeyboardShortcutEnabled: jest.fn(() => true),
}));
-const issuesHandler = new IssuesServiceMock();
+const usersHandler = new UsersServiceMock();
+const issuesHandler = new IssuesServiceMock(usersHandler);
beforeEach(() => {
issuesHandler.reset();
+ usersHandler.reset();
+ usersHandler.users = [mockLoggedInUser() as unknown as RestUserDetailed];
});
describe('rendering', () => {
transitions: [IssueTransition.Confirm, IssueTransition.UnConfirm],
});
issuesHandler.setIssueList([{ issue, snippets: {} }]);
- issuesHandler.setCurrentUser(mockLoggedInUser({ login: 'leia', name: 'Organa' }));
+ usersHandler.setCurrentUser(mockLoggedInUser({ login: 'leia', name: 'Organa' }));
renderIssue({
onCheck,
selected: true,
export function useUsersQueries<U extends RestUserBase>(
getParams: Omit<Parameters<typeof getUsers>[0], 'pageSize' | 'pageIndex'>,
+ enabled = true,
) {
return useInfiniteQuery({
queryKey: ['user', 'list', getParams],
queryFn: ({ pageParam = 1 }) => getUsers<U>({ ...getParams, pageIndex: pageParam }),
getNextPageParam,
getPreviousPageParam,
+ enabled,
});
}