aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorViktor Vorona <viktor.vorona@sonarsource.com>2024-01-05 14:03:26 +0100
committersonartech <sonartech@sonarsource.com>2024-01-17 20:02:45 +0000
commit56afeba6b92a8bc67b8c60f0812c824c6874029d (patch)
tree46a1c85f8a8e6f5a6d55bc0f7fc07b9525478130
parente9fa96f54d2e21bafde5dcd9fb9d388df914cd08 (diff)
downloadsonarqube-56afeba6b92a8bc67b8c60f0812c824c6874029d.tar.gz
sonarqube-56afeba6b92a8bc67b8c60f0812c824c6874029d.zip
SONAR-21381 Show author in Issue activity
-rw-r--r--server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts50
-rw-r--r--server/sonar-web/src/main/js/api/mocks/UsersServiceMock.ts33
-rw-r--r--server/sonar-web/src/main/js/api/mocks/data/issues.ts2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-Filtering-it.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssuesAppActivity-it.tsx34
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssuesAppGuide-it.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssuesNewStatusAndTransitionGuide-it.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssuesSourceViewer-it.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssueReviewHistory.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/utils.ts13
-rw-r--r--server/sonar-web/src/main/js/apps/issues/test-utils.tsx4
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx8
-rw-r--r--server/sonar-web/src/main/js/components/issue/__tests__/Issue-it.tsx9
-rw-r--r--server/sonar-web/src/main/js/queries/users.ts2
16 files changed, 119 insertions, 66 deletions
diff --git a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
index 4400f4a3e8a..a451cf43a1f 100644
--- a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
+++ b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
@@ -47,7 +47,6 @@ import {
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,
@@ -57,8 +56,8 @@ import {
getIssueFlowSnippets,
listIssues,
searchIssueAuthors,
- searchIssueTags,
searchIssues,
+ searchIssueTags,
setIssueAssignee,
setIssueSeverity,
setIssueTags,
@@ -66,15 +65,14 @@ import {
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 {
@@ -101,14 +99,14 @@ function generateReferenceComponentsForIssues(issueData: IssueData[]) {
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();
@@ -117,9 +115,7 @@ export default class IssuesServiceMock {
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);
@@ -128,7 +124,6 @@ export default class IssuesServiceMock {
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);
@@ -138,11 +133,6 @@ export default class IssuesServiceMock {
reset = () => {
this.list = cloneDeep(this.defaultList);
- this.currentUser = mockLoggedInUser();
- };
-
- setCurrentUser = (user: LoggedInUser) => {
- this.currentUser = user;
};
setIssueList = (list: IssueData[]) => {
@@ -496,24 +486,6 @@ export default class IssuesServiceMock {
});
};
- 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);
};
@@ -524,7 +496,10 @@ export default class IssuesServiceMock {
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,
);
};
@@ -628,13 +603,6 @@ export default class IssuesServiceMock {
);
};
- handleGetUsers = () => {
- return this.reply({
- page: mockPaging(),
- users: [mockLoggedInUser() as unknown as RestUser],
- });
- };
-
handleSearchIssueAuthors = () => {
return this.reply(mockIssueAuthors());
};
diff --git a/server/sonar-web/src/main/js/api/mocks/UsersServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/UsersServiceMock.ts
index 77dc8e62171..1d0e13dac7c 100644
--- a/server/sonar-web/src/main/js/api/mocks/UsersServiceMock.ts
+++ b/server/sonar-web/src/main/js/api/mocks/UsersServiceMock.ts
@@ -20,15 +20,21 @@
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,
@@ -131,6 +137,7 @@ const DEFAULT_PASSWORD = 'test';
export default class UsersServiceMock {
isManaged = true;
users = cloneDeep(DEFAULT_USERS);
+ currentUser = mockLoggedInUser();
groups = cloneDeep(DEFAULT_GROUPS);
password = DEFAULT_PASSWORD;
groupMembershipsServiceMock?: GroupMembershipsServiceMock = undefined;
@@ -145,7 +152,8 @@ export default class UsersServiceMock {
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]) => {
@@ -178,7 +186,7 @@ export default class UsersServiceMock {
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;
}
@@ -354,11 +362,28 @@ export default class UsersServiceMock {
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> {
diff --git a/server/sonar-web/src/main/js/api/mocks/data/issues.ts b/server/sonar-web/src/main/js/api/mocks/data/issues.ts
index 129806d01f5..3efa63ae18d 100644
--- a/server/sonar-web/src/main/js/api/mocks/data/issues.ts
+++ b/server/sonar-web/src/main/js/api/mocks/data/issues.ts
@@ -320,6 +320,7 @@ export function mockIssuesList(baseComponentKey = PARENT_COMPONENT_KEY): IssueDa
status: IssueDeprecatedStatus.Open,
issueStatus: IssueStatus.Open,
ruleDescriptionContextKey: 'spring',
+ author: 'bob.marley@test.com',
}),
snippets: keyBy(
[
@@ -347,6 +348,7 @@ export function mockIssuesList(baseComponentKey = PARENT_COMPONENT_KEY): IssueDa
resolution: IssueResolution.Fixed,
status: IssueDeprecatedStatus.Confirmed,
issueStatus: IssueStatus.Confirmed,
+ author: 'unknownemail@test.com',
}),
snippets: keyBy(
[
diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
index 0ec30c428fb..ad7246fa8b2 100644
--- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
@@ -21,7 +21,9 @@ import { screen, within } from '@testing-library/react';
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,
@@ -29,6 +31,7 @@ import {
renderIssueApp,
renderProjectIssuesApp,
ui,
+ usersHandler,
} from '../test-utils';
jest.mock('../sidebar/Sidebar', () => {
@@ -58,6 +61,8 @@ beforeEach(() => {
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();
});
diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-Filtering-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-Filtering-it.tsx
index 1a62b0f35f3..66670341b52 100644
--- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-Filtering-it.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-Filtering-it.tsx
@@ -31,6 +31,7 @@ import {
renderIssueApp,
renderProjectIssuesApp,
ui,
+ usersHandler,
waitOnDataLoaded,
} from '../test-utils';
@@ -56,6 +57,7 @@ beforeEach(() => {
issuesHandler.reset();
componentsHandler.reset();
branchHandler.reset();
+ usersHandler.reset();
window.scrollTo = jest.fn();
window.HTMLElement.prototype.scrollTo = jest.fn();
});
@@ -192,7 +194,7 @@ describe('issues app filtering', () => {
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);
@@ -229,7 +231,7 @@ describe('issues app filtering', () => {
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();
diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx
index 5ebcbd4d4e6..49633d8a9bf 100644
--- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx
@@ -31,6 +31,7 @@ import {
renderIssueApp,
renderProjectIssuesApp,
ui,
+ usersHandler,
} from '../test-utils';
jest.mock('../sidebar/Sidebar', () => {
@@ -48,6 +49,7 @@ beforeEach(() => {
issuesHandler.reset();
componentsHandler.reset();
branchHandler.reset();
+ usersHandler.reset();
window.scrollTo = jest.fn();
window.HTMLElement.prototype.scrollTo = jest.fn();
});
@@ -221,7 +223,7 @@ describe('issues app', () => {
dismissedNotices: { [NoticeType.ISSUE_GUIDE]: true },
});
issuesHandler.setIsAdmin(true);
- issuesHandler.setCurrentUser(currentUser);
+ usersHandler.setCurrentUser(currentUser);
renderIssueApp(currentUser);
// Check that the bulk button has correct behavior
@@ -250,7 +252,7 @@ describe('issues app', () => {
dismissedNotices: { [NoticeType.ISSUE_GUIDE]: true },
});
issuesHandler.setIsAdmin(true);
- issuesHandler.setCurrentUser(currentUser);
+ usersHandler.setCurrentUser(currentUser);
renderIssueApp(currentUser);
// Check that we bulk change the selected issue
diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesAppActivity-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesAppActivity-it.tsx
index 7d679a4a39c..5c6fec5e121 100644
--- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesAppActivity-it.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesAppActivity-it.tsx
@@ -20,7 +20,15 @@
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 = () => {
@@ -49,6 +57,14 @@ beforeEach(() => {
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();
});
@@ -103,7 +119,7 @@ it('should be able to show changelog', async () => {
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)',
@@ -125,3 +141,17 @@ it('should be able to show changelog', async () => {
),
).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',
+ );
+});
diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesAppGuide-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesAppGuide-it.tsx
index 9e794a924b8..636b22240ee 100644
--- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesAppGuide-it.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesAppGuide-it.tsx
@@ -28,6 +28,7 @@ import {
renderIssueApp,
renderProjectIssuesApp,
ui,
+ usersHandler,
} from '../test-utils';
jest.mock('../sidebar/Sidebar', () => {
@@ -57,6 +58,7 @@ beforeEach(() => {
issuesHandler.reset();
componentsHandler.reset();
branchHandler.reset();
+ usersHandler.reset();
window.scrollTo = jest.fn();
window.HTMLElement.prototype.scrollTo = jest.fn();
});
diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesNewStatusAndTransitionGuide-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesNewStatusAndTransitionGuide-it.tsx
index 3700e006c8e..0a95a39f2cc 100644
--- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesNewStatusAndTransitionGuide-it.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesNewStatusAndTransitionGuide-it.tsx
@@ -20,7 +20,6 @@
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';
@@ -29,9 +28,7 @@ import { IssueTransition } from '../../../types/issues';
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();
diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesSourceViewer-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesSourceViewer-it.tsx
index 1facb45f6f3..59e2a997b66 100644
--- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesSourceViewer-it.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesSourceViewer-it.tsx
@@ -24,12 +24,14 @@ import {
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();
});
diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssueReviewHistory.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssueReviewHistory.tsx
index 400e3fe27e2..9bdc3e17d9b 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/IssueReviewHistory.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/IssueReviewHistory.tsx
@@ -39,7 +39,7 @@ import { sanitizeUserInput } from '../../../helpers/sanitize';
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;
@@ -50,7 +50,7 @@ export interface HotspotReviewHistoryProps {
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('');
diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/utils.ts b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/utils.ts
index 135d51c4aa9..7b9d057d675 100644
--- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/utils.ts
+++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/utils.ts
@@ -20,6 +20,7 @@
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 {
@@ -33,6 +34,7 @@ import {
SnippetsByComponent,
SourceLine,
} from '../../../types/types';
+import { RestUser } from '../../../types/users';
const LINES_ABOVE = 5;
const LINES_BELOW = 5;
@@ -235,20 +237,23 @@ export function inSnippet(line: number, snippet: SourceLine[]) {
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,
},
});
}
diff --git a/server/sonar-web/src/main/js/apps/issues/test-utils.tsx b/server/sonar-web/src/main/js/apps/issues/test-utils.tsx
index 0c3ed20a595..1b5d7cd3a51 100644
--- a/server/sonar-web/src/main/js/apps/issues/test-utils.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/test-utils.tsx
@@ -23,6 +23,7 @@ import { Outlet, Route } from 'react-router-dom';
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';
@@ -38,7 +39,8 @@ import { NoticeType } from '../../types/users';
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();
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx
index 9e76f47e07f..f29f2d82078 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx
@@ -22,9 +22,11 @@ import userEvent from '@testing-library/user-event';
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';
@@ -33,7 +35,6 @@ 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');
jest.mock('../helpers/loadIssues', () => ({
__esModule: true,
@@ -50,11 +51,14 @@ jest.mock('../helpers/lines', () => {
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 () => {
diff --git a/server/sonar-web/src/main/js/components/issue/__tests__/Issue-it.tsx b/server/sonar-web/src/main/js/components/issue/__tests__/Issue-it.tsx
index 89638fad596..eb03a1dc21e 100644
--- a/server/sonar-web/src/main/js/components/issue/__tests__/Issue-it.tsx
+++ b/server/sonar-web/src/main/js/components/issue/__tests__/Issue-it.tsx
@@ -23,6 +23,7 @@ import { omit, pick } from 'lodash';
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';
@@ -35,16 +36,20 @@ import {
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', () => {
@@ -147,7 +152,7 @@ it('should correctly handle keyboard shortcuts', async () => {
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,
diff --git a/server/sonar-web/src/main/js/queries/users.ts b/server/sonar-web/src/main/js/queries/users.ts
index 0ba1e2737cd..1a97eaeb45e 100644
--- a/server/sonar-web/src/main/js/queries/users.ts
+++ b/server/sonar-web/src/main/js/queries/users.ts
@@ -37,12 +37,14 @@ const STALE_TIME = 4 * 60 * 1000;
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,
});
}