From 56afeba6b92a8bc67b8c60f0812c824c6874029d Mon Sep 17 00:00:00 2001 From: Viktor Vorona Date: Fri, 5 Jan 2024 14:03:26 +0100 Subject: [PATCH] SONAR-21381 Show author in Issue activity --- .../main/js/api/mocks/IssuesServiceMock.ts | 50 ++++--------------- .../src/main/js/api/mocks/UsersServiceMock.ts | 33 ++++++++++-- .../src/main/js/api/mocks/data/issues.ts | 2 + .../js/apps/issues/__tests__/IssueApp-it.tsx | 5 ++ .../__tests__/IssuesApp-Filtering-it.tsx | 6 ++- .../js/apps/issues/__tests__/IssuesApp-it.tsx | 6 ++- .../issues/__tests__/IssuesAppActivity-it.tsx | 34 ++++++++++++- .../issues/__tests__/IssuesAppGuide-it.tsx | 2 + .../IssuesNewStatusAndTransitionGuide-it.tsx | 5 +- .../__tests__/IssuesSourceViewer-it.tsx | 2 + .../issues/components/IssueReviewHistory.tsx | 4 +- .../crossComponentSourceViewer/utils.ts | 13 +++-- .../src/main/js/apps/issues/test-utils.tsx | 4 +- .../__tests__/SourceViewer-it.tsx | 8 ++- .../components/issue/__tests__/Issue-it.tsx | 9 +++- server/sonar-web/src/main/js/queries/users.ts | 2 + 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) { 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[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(response: T): Promise { 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([]); - 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({ 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( getParams: Omit[0], 'pageSize' | 'pageIndex'>, + enabled = true, ) { return useInfiniteQuery({ queryKey: ['user', 'list', getParams], queryFn: ({ pageParam = 1 }) => getUsers({ ...getParams, pageIndex: pageParam }), getNextPageParam, getPreviousPageParam, + enabled, }); } -- 2.39.5