From 90ad2c4384f1d9a55af1b28efcd9886efbce98db Mon Sep 17 00:00:00 2001 From: stanislavh Date: Wed, 20 Dec 2023 12:11:16 +0100 Subject: [PATCH] SONAR-21259 Show fixed issues in issues list of the target branch --- .../js/apps/issues/__tests__/IssuesApp-it.tsx | 6 ++ .../js/apps/issues/__tests__/utils-test.ts | 2 + .../js/apps/issues/components/IssuesApp.tsx | 19 ++++-- .../issues/components/IssuesListTitle.tsx | 66 +++++++++++++++++++ .../src/main/js/apps/issues/test-utils.tsx | 5 +- .../src/main/js/apps/issues/utils.ts | 3 + .../pullRequests/IssueMeasuresCard.tsx | 2 +- .../src/main/js/helpers/mocks/issues.ts | 1 + .../sonar-web/src/main/js/queries/branch.tsx | 1 + .../resources/org/sonar/l10n/core.properties | 2 + 10 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/issues/components/IssuesListTitle.tsx 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 18bd744d866..5ebcbd4d4e6 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 @@ -66,6 +66,12 @@ describe('issues app', () => { expect(screen.getByText('issues.not_all_issue_show')).toBeInTheDocument(); }); + + it('should show fixed issues message', async () => { + renderProjectIssuesApp('project/issues?id=my-project&fixedInPullRequest=01'); + + expect(await ui.fixedIssuesHeading.find()).toBeInTheDocument(); + }); }); describe('navigation', () => { diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts index 28a2fbb9737..7f580d8fe53 100644 --- a/server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts @@ -72,6 +72,7 @@ describe('serialize/deserialize', () => { issueStatuses: [IssueStatus.Accepted, IssueStatus.Confirmed], tags: ['a', 'b'], types: ['a', 'b'], + fixedInPullRequest: '', }), ).toStrictEqual({ assignees: 'a,b', @@ -152,6 +153,7 @@ describe('serialize/deserialize', () => { issueStatuses: [], tags: [], types: [], + fixedInPullRequest: '', }); }); diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx index 29509e3b0e3..60362397ded 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx @@ -52,7 +52,12 @@ import IssueTabViewer from '../../../components/rules/IssueTabViewer'; import '../../../components/search-navigator.css'; import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils'; import Spinner from '../../../components/ui/Spinner'; -import { fillBranchLike, getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branch-like'; +import { + fillBranchLike, + getBranchLikeQuery, + isPullRequest, + isSameBranchLike, +} from '../../../helpers/branch-like'; import handleRequiredAuthentication from '../../../helpers/handleRequiredAuthentication'; import { parseIssueFromResponse } from '../../../helpers/issues'; import { isInput, isShortcut } from '../../../helpers/keyboardEventHelpers'; @@ -104,6 +109,7 @@ import IssueGuide from './IssueGuide'; import IssueNewStatusAndTransitionGuide from './IssueNewStatusAndTransitionGuide'; import IssueReviewHistoryAndComments from './IssueReviewHistoryAndComments'; import IssuesList from './IssuesList'; +import IssuesListTitle from './IssuesListTitle'; import IssuesSourceViewer from './IssuesSourceViewer'; import NoIssues from './NoIssues'; import NoMyIssues from './NoMyIssues'; @@ -112,6 +118,7 @@ import { PSEUDO_SHADOW_HEIGHT } from './StyledHeader'; interface Props extends WithIndexationContextProps { branchLike?: BranchLike; + branchLikes?: BranchLike[]; component?: Component; currentUser: CurrentUser; isFetchingBranch?: boolean; @@ -1128,8 +1135,8 @@ export class App extends React.PureComponent { } renderList() { - const { branchLike, component, currentUser } = this.props; - const { issues, loading, loadingMore, openIssue, paging } = this.state; + const { branchLike, component, currentUser, branchLikes } = this.props; + const { issues, loading, loadingMore, openIssue, paging, query } = this.state; const selectedIndex = this.getSelectedIndex(); const selectedIssue = selectedIndex !== undefined ? issues[selectedIndex] : undefined; @@ -1151,7 +1158,11 @@ export class App extends React.PureComponent { return (
-

{translate('list_of_issues')}

+ {issues.length > 0 && ( ) { + const intl = useIntl(); + const pullRequest = pullRequests.find((pr) => pr.key === fixedInPullRequest); + const prSummaryUrl = getPullRequestUrl(component?.key ?? '', fixedInPullRequest); + + return pullRequest && !component?.needIssueSync ? ( + <> + + {intl.formatMessage({ id: 'issues.fixed_issues' })} + + + {intl.formatMessage( + { id: 'issues.fixed_issues.description' }, + { + pullRequest: ( + <> + + + {pullRequest.title} + + + ), + }, + )} + + + ) : ( +

{intl.formatMessage({ id: 'list_of_issues' })}

+ ); +} 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 9db0654d83b..0c3ed20a595 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 @@ -32,6 +32,7 @@ import { SoftwareImpactSeverity, SoftwareQuality, } from '../../types/clean-code-taxonomy'; +import { Feature } from '../../types/features'; import { Component } from '../../types/types'; import { NoticeType } from '../../types/users'; import IssuesApp from './components/IssuesApp'; @@ -55,6 +56,8 @@ export const ui = { issueItems: byRole('region'), + fixedIssuesHeading: byRole('heading', { level: 2, name: 'issues.fixed_issues' }), + issueItem1: byRole('region', { name: 'Issue with no location message' }), issueItem2: byRole('region', { name: 'FlowIssue' }), issueItem3: byRole('region', { name: 'Issue on file' }), @@ -186,7 +189,7 @@ export function renderProjectIssuesApp( {projectIssuesRoutes()} ), - { navigateTo, currentUser }, + { navigateTo, currentUser, featureList: [Feature.BranchSupport] }, { component: mockComponent(overrides) }, ); } diff --git a/server/sonar-web/src/main/js/apps/issues/utils.ts b/server/sonar-web/src/main/js/apps/issues/utils.ts index a002c3ffb22..a2e303bfb5a 100644 --- a/server/sonar-web/src/main/js/apps/issues/utils.ts +++ b/server/sonar-web/src/main/js/apps/issues/utils.ts @@ -66,6 +66,7 @@ export interface Query { cwe: string[]; directories: string[]; files: string[]; + fixedInPullRequest: string; impactSeverities: SoftwareImpactSeverity[]; impactSoftwareQualities: SoftwareQuality[]; issues: string[]; @@ -134,6 +135,7 @@ export function parseQuery(query: RawQuery): Query { tags: parseAsArray(query.tags, parseAsString), types: parseAsArray(query.types, parseAsString), codeVariants: parseAsArray(query.codeVariants, parseAsString), + fixedInPullRequest: parseAsString(query.fixedInPullRequest), }; } @@ -222,6 +224,7 @@ export function serializeQuery(query: Query): RawQuery { cwe: serializeStringArray(query.cwe), directories: serializeStringArray(query.directories), files: serializeStringArray(query.files), + fixedInPullRequest: serializeString(query.fixedInPullRequest), issues: serializeStringArray(query.issues), languages: serializeStringArray(query.languages), owaspTop10: serializeStringArray(query.owaspTop10), diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/IssueMeasuresCard.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/IssueMeasuresCard.tsx index 12efb13cf87..434b05076ee 100644 --- a/server/sonar-web/src/main/js/apps/overview/pullRequests/IssueMeasuresCard.tsx +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/IssueMeasuresCard.tsx @@ -71,7 +71,7 @@ export default function IssueMeasuresCard( }); const fixedUrl = getComponentIssuesUrl(component.key, { branch: pullRequest.target, - fixedInPR: pullRequest.key, + fixedInPullRequest: pullRequest.key, }); const acceptedUrl = getComponentIssuesUrl(component.key, { ...getBranchLikeQuery(pullRequest), diff --git a/server/sonar-web/src/main/js/helpers/mocks/issues.ts b/server/sonar-web/src/main/js/helpers/mocks/issues.ts index 7d7cf3460f7..0379800351b 100644 --- a/server/sonar-web/src/main/js/helpers/mocks/issues.ts +++ b/server/sonar-web/src/main/js/helpers/mocks/issues.ts @@ -66,6 +66,7 @@ export function mockQuery(overrides: Partial = {}): Query { cwe: [], directories: [], files: [], + fixedInPullRequest: '', issues: [], languages: [], owaspTop10: [], diff --git a/server/sonar-web/src/main/js/queries/branch.tsx b/server/sonar-web/src/main/js/queries/branch.tsx index 918f0801fa8..a58ac2ceef6 100644 --- a/server/sonar-web/src/main/js/queries/branch.tsx +++ b/server/sonar-web/src/main/js/queries/branch.tsx @@ -109,6 +109,7 @@ function getContext(key: ReturnType) { export function useBranchesQuery(component?: LightComponent, refetchInterval?: number) { const features = useContext(AvailableFeaturesContext); const key = useBranchesQueryKey(InnerState.Details, component?.key); + return useQuery({ queryKey: key, queryFn: async ({ queryKey: [, key, prOrBranch, name] }) => { diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index cbd79aa6161..6651b1035ac 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -1111,6 +1111,8 @@ issues.max_new_code_period=Max New Code Period issues.my_issues=My Issues issues.no_my_issues=There are no issues assigned to you. issues.no_issues=No Issues. Hooray! +issues.fixed_issues=Fixed issues +issues.fixed_issues.description=List of issues that will be fixed by {pullRequest} issues.x_more_locations=+ {0} more locations issues.not_all_issue_show=Not all issues are included issues.not_all_issue_show_why=You do not have access to all projects in this portfolio -- 2.39.5