Browse Source

SONAR-20667 Display a message at the top of the project overview when a recent SQ upgrade updated some of the project's QPs

tags/10.3.0.82913
Ambroise C 8 months ago
parent
commit
f9f06dd597

+ 1
- 1
server/sonar-web/src/main/js/apps/overview/branches/ActivityPanel.tsx View File

@@ -52,7 +52,7 @@ export interface ActivityPanelProps {
onGraphChange: (graph: GraphType) => void;
}

const MAX_ANALYSES_NB = 5;
export const MAX_ANALYSES_NB = 5;
const MAX_GRAPH_NB = 2;
const MAX_SERIES_PER_GRAPH = 3;


+ 1
- 0
server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx View File

@@ -104,6 +104,7 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp
<div className="sw-flex-1">
<div className="sw-flex sw-flex-col sw-pt-6">
<MeasuresPanel
analyses={analyses}
appLeak={appLeak}
branch={branch}
component={component}

+ 1
- 1
server/sonar-web/src/main/js/apps/overview/branches/Event.tsx View File

@@ -42,7 +42,7 @@ export function Event({ event }: Props) {

if (event.category === ProjectAnalysisEventCategory.SqUpgrade) {
return (
<FlagMessage className="sw-my-1" variant="info">
<FlagMessage className="sw-my-1" id={event.key} variant="info">
<FormattedMessage
id="event.sqUpgrade"
values={{

+ 63
- 0
server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanel.tsx View File

@@ -18,7 +18,9 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

import { isBefore, sub } from 'date-fns';
import {
ButtonLink,
Card,
CoverageIndicator,
DuplicationsIndicator,
@@ -29,6 +31,7 @@ import {
ToggleButton,
} from 'design-system';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import DocLink from '../../../components/common/DocLink';
import ComponentReportActions from '../../../components/controls/ComponentReportActions';
import { Location, withRouter } from '../../../components/hoc/withRouter';
@@ -41,15 +44,18 @@ import { Branch } from '../../../types/branch-like';
import { ComponentQualifier } from '../../../types/component';
import { IssueType } from '../../../types/issues';
import { MetricKey } from '../../../types/metrics';
import { Analysis, ProjectAnalysisEventCategory } from '../../../types/project-activity';
import { QualityGateStatus } from '../../../types/quality-gates';
import { Component, MeasureEnhanced, Period } from '../../../types/types';
import { MeasurementType, parseQuery } from '../utils';
import { MAX_ANALYSES_NB } from './ActivityPanel';
import { LeakPeriodInfo } from './LeakPeriodInfo';
import MeasuresPanelIssueMeasure from './MeasuresPanelIssueMeasure';
import MeasuresPanelNoNewCode from './MeasuresPanelNoNewCode';
import MeasuresPanelPercentMeasure from './MeasuresPanelPercentMeasure';

export interface MeasuresPanelProps {
analyses?: Analysis[];
appLeak?: ApplicationPeriod;
branch?: Branch;
component: Component;
@@ -65,8 +71,11 @@ export enum MeasuresPanelTabs {
Overall = 'overall',
}

const SQ_UPGRADE_NOTIFICATION_TIMEOUT = { weeks: 3 };

export function MeasuresPanel(props: MeasuresPanelProps) {
const {
analyses,
appLeak,
branch,
component,
@@ -94,6 +103,43 @@ export function MeasuresPanel(props: MeasuresPanelProps) {

const isNewCodeTab = tab === MeasuresPanelTabs.New;

const recentSqUpgradeEvent = React.useMemo(() => {
if (!analyses || analyses.length === 0) {
return undefined;
}

const notificationExpirationTime = sub(new Date(), SQ_UPGRADE_NOTIFICATION_TIMEOUT);

for (const analysis of analyses.slice(0, MAX_ANALYSES_NB)) {
if (isBefore(new Date(analysis.date), notificationExpirationTime)) {
return undefined;
}

let sqUpgradeEvent = undefined;
let hasQpUpdateEvent = false;
for (const event of analysis.events) {
sqUpgradeEvent =
event.category === ProjectAnalysisEventCategory.SqUpgrade ? event : sqUpgradeEvent;
hasQpUpdateEvent =
hasQpUpdateEvent || event.category === ProjectAnalysisEventCategory.QualityProfile;

if (sqUpgradeEvent !== undefined && hasQpUpdateEvent) {
return sqUpgradeEvent;
}
}
}

return undefined;
}, [analyses]);

const scrollToLatestSqUpgradeEvent = () => {
document.querySelector(`#${recentSqUpgradeEvent?.key}`)?.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center',
});
};

React.useEffect(() => {
// Open Overall tab by default if there are no new measures.
if (loading === false && !hasDiffMeasures && isNewCodeTab) {
@@ -132,6 +178,23 @@ export function MeasuresPanel(props: MeasuresPanelProps) {
</div>
) : (
<>
{recentSqUpgradeEvent && (
<div>
<FlagMessage className="sw-mb-4" variant="info">
<FormattedMessage
id="overview.quality_profiles_update_after_sq_upgrade.message"
values={{
link: (
<ButtonLink className="sw-ml-1" onClick={scrollToLatestSqUpgradeEvent}>
<FormattedMessage id="overview.quality_profiles_update_after_sq_upgrade.link" />
</ButtonLink>
),
sqVersion: recentSqUpgradeEvent.name,
}}
/>
</FlagMessage>
</div>
)}
<div className="sw-flex sw-items-center">
<ToggleButton onChange={(key) => selectTab(key)} options={tabs} value={tab} />
{failingConditions > 0 && (

+ 96
- 3
server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx View File

@@ -31,7 +31,7 @@ import { getActivityGraph, saveActivityGraph } from '../../../../components/acti
import { isDiffMetric } from '../../../../helpers/measures';
import { mockMainBranch } from '../../../../helpers/mocks/branch-like';
import { mockComponent } from '../../../../helpers/mocks/component';
import { mockAnalysis } from '../../../../helpers/mocks/project-activity';
import { mockAnalysis, mockAnalysisEvent } from '../../../../helpers/mocks/project-activity';
import {
mockQualityGateApplicationStatus,
mockQualityGateProjectStatus,
@@ -40,8 +40,12 @@ import { mockLoggedInUser, mockPeriod } from '../../../../helpers/testMocks';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import { ComponentQualifier } from '../../../../types/component';
import { MetricKey, MetricType } from '../../../../types/metrics';
import { GraphType } from '../../../../types/project-activity';
import { CaycStatus, Measure, Metric } from '../../../../types/types';
import {
Analysis,
GraphType,
ProjectAnalysisEventCategory,
} from '../../../../types/project-activity';
import { CaycStatus, Measure, Metric, Paging } from '../../../../types/types';
import BranchOverview, { BRANCH_OVERVIEW_ACTIVITY_GRAPH, NO_CI_DETECTED } from '../BranchOverview';

jest.mock('../../../../api/measures', () => {
@@ -402,6 +406,95 @@ it.each([
},
);

it.each([
[
'no upgrade event',
[
mockAnalysis({
events: [mockAnalysisEvent({ category: ProjectAnalysisEventCategory.Other })],
}),
],
false,
],
[
'upgrade event too old',
[
mockAnalysis({
date: '2023-04-02T12:10:30+0200',
events: [mockAnalysisEvent({ category: ProjectAnalysisEventCategory.SqUpgrade })],
}),
],
false,
],
[
'upgrade event too far down in the list',
[
mockAnalysis({
date: '2023-04-13T08:10:30+0200',
}),
mockAnalysis({
date: '2023-04-13T09:10:30+0200',
}),
mockAnalysis({
date: '2023-04-13T10:10:30+0200',
}),
mockAnalysis({
date: '2023-04-13T11:10:30+0200',
}),
mockAnalysis({
date: '2023-04-13T12:10:30+0200',
events: [mockAnalysisEvent({ category: ProjectAnalysisEventCategory.SqUpgrade })],
}),
],
false,
],
[
'upgrade event without QP update event',
[
mockAnalysis({
date: '2023-04-13T12:10:30+0200',
events: [mockAnalysisEvent({ category: ProjectAnalysisEventCategory.SqUpgrade })],
}),
],
false,
],
[
'upgrade event with QP update event',
[
mockAnalysis({
date: '2023-04-13T12:10:30+0200',
events: [
mockAnalysisEvent({ category: ProjectAnalysisEventCategory.SqUpgrade }),
mockAnalysisEvent({ category: ProjectAnalysisEventCategory.QualityProfile }),
],
}),
],
true,
],
])(
'should correctly display message about SQ upgrade updating QPs',
async (_, analyses, expected) => {
jest.useFakeTimers({
advanceTimers: true,
now: new Date('2023-04-25T12:00:00+0200'),
});

jest.mocked(getProjectActivity).mockResolvedValueOnce({
analyses,
} as { analyses: Analysis[]; paging: Paging });

renderBranchOverview();

await screen.findByText('overview.quality_gate.status');

expect(
screen.queryByText(/overview.quality_profiles_update_after_sq_upgrade.message/) !== null,
).toBe(expected);

jest.useRealTimers();
},
);

it('should correctly handle graph type storage', async () => {
renderBranchOverview();


+ 3
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -3872,6 +3872,9 @@ overview.badges.leak_warning=Project badges can expose your security rating and
overview.badges.renew=Renew Token
overview.badges.renew.description=If your project badge security token has leaked to an unsafe environment, you can renew it:

overview.quality_profiles_update_after_sq_upgrade.message=Upgrade to SonarQube {sqVersion} has updated your Quality Profiles. Issues on your project may have been affected. {link}
overview.quality_profiles_update_after_sq_upgrade.link=See more details


#------------------------------------------------------------------------------
#

Loading…
Cancel
Save