diff options
author | stanislavh <stanislav.honcharov@sonarsource.com> | 2023-12-20 12:11:16 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-01-17 20:02:44 +0000 |
commit | 90ad2c4384f1d9a55af1b28efcd9886efbce98db (patch) | |
tree | 02e89f7ebea4592a9a7f8a400b7db0b72edc5a19 | |
parent | a8336afac1068fdc4f79f65284f52511ca471964 (diff) | |
download | sonarqube-90ad2c4384f1d9a55af1b28efcd9886efbce98db.tar.gz sonarqube-90ad2c4384f1d9a55af1b28efcd9886efbce98db.zip |
SONAR-21259 Show fixed issues in issues list of the target branch
10 files changed, 101 insertions, 6 deletions
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<Props, State> { } 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<Props, State> { return ( <div> - <h2 className="sw-sr-only">{translate('list_of_issues')}</h2> + <IssuesListTitle + fixedInPullRequest={query.fixedInPullRequest} + pullRequests={branchLikes?.filter(isPullRequest) ?? []} + component={component} + /> {issues.length > 0 && ( <IssuesList diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesListTitle.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesListTitle.tsx new file mode 100644 index 00000000000..6214d26e4a9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesListTitle.tsx @@ -0,0 +1,66 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { DiscreetLink, LightPrimary, PullRequestIcon, SubTitle } from 'design-system'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import { getPullRequestUrl } from '../../../helpers/urls'; +import { PullRequest } from '../../../types/branch-like'; +import { Component } from '../../../types/types'; + +interface IssuesListTitleProps { + component?: Component; + fixedInPullRequest: string; + pullRequests: PullRequest[]; +} + +export default function IssuesListTitle({ + fixedInPullRequest, + pullRequests, + component, +}: Readonly<IssuesListTitleProps>) { + const intl = useIntl(); + const pullRequest = pullRequests.find((pr) => pr.key === fixedInPullRequest); + const prSummaryUrl = getPullRequestUrl(component?.key ?? '', fixedInPullRequest); + + return pullRequest && !component?.needIssueSync ? ( + <> + <SubTitle className="sw-mt-6 sw-mb-2"> + {intl.formatMessage({ id: 'issues.fixed_issues' })} + </SubTitle> + <LightPrimary className="sw-flex sw-items-center sw-gap-1 sw-mb-2"> + {intl.formatMessage( + { id: 'issues.fixed_issues.description' }, + { + pullRequest: ( + <> + <PullRequestIcon /> + <DiscreetLink to={prSummaryUrl} className="sw-mt-[3px]"> + {pullRequest.title} + </DiscreetLink> + </> + ), + }, + )} + </LightPrimary> + </> + ) : ( + <h2 className="sw-sr-only">{intl.formatMessage({ id: 'list_of_issues' })}</h2> + ); +} 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()} </Route> ), - { 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> = {}): 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<typeof useBranchesQueryKey>) { 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 |