From 51c3da2b324930e5ebaf5ea6e7213ba6b31b885b Mon Sep 17 00:00:00 2001 From: Ambroise C Date: Wed, 13 Sep 2023 17:24:05 +0200 Subject: [PATCH] SONAR-20327 Fix code smells following prettier upgrades Co-authored-by: David Cho-Lerat --- server/sonar-web/src/main/js/api/issues.ts | 22 +-- .../js/api/mocks/CodingRulesServiceMock.ts | 63 +++++-- .../src/main/js/api/mocks/data/ids.ts | 1 + .../main/js/api/mocks/data/qualityProfiles.ts | 11 +- .../__tests__/RecentHistory-test.tsx | 40 ++-- .../IndexationNotificationHelper.ts | 2 +- .../UpdateNotification.tsx | 6 +- .../main/js/apps/background-tasks/utils.ts | 2 +- .../js/apps/code/__tests__/utils-test.tsx | 47 +++-- .../sonar-web/src/main/js/apps/code/utils.ts | 4 +- .../coding-rules/__tests__/CodingRules-it.ts | 22 ++- .../coding-rules/components/BulkChange.tsx | 4 +- .../components/BulkChangeModal.tsx | 7 +- .../components/RuleDetailsIssues.tsx | 6 +- .../components/RuleDetailsProfiles.tsx | 13 +- .../main/js/apps/coding-rules/utils-tests.tsx | 3 + .../issues/sidebar/__tests__/Sidebar-it.tsx | 30 +++ .../components/ProjectActivityAnalysis.tsx | 4 +- .../components/BranchListRow.tsx | 7 +- .../ProjectQualityProfilesApp.tsx | 39 ++-- .../LanguageProfileSelectOption.tsx | 33 ++-- .../js/apps/projects/__tests__/utils-test.ts | 14 +- .../src/main/js/apps/projects/query.ts | 12 +- .../quality-gates/__tests__/utils-test.ts | 10 +- .../ConditionReviewAndUpdateModal.tsx | 4 +- .../components/ConditionValueDescription.tsx | 14 +- .../quality-profiles/home/EvolutionRules.tsx | 6 +- .../main/js/apps/security-hotspots/utils.ts | 4 +- .../js/apps/web-api/components/WebApiApp.tsx | 8 +- .../apps/webhooks/components/DeliveryItem.tsx | 4 +- .../components/MeasuresOverlay.tsx | 18 +- .../__tests__/ActivityGraph-it.tsx | 13 +- .../controls/ComponentReportActions.tsx | 2 +- .../js/components/controls/ListFooter.tsx | 6 +- .../main/js/components/controls/Select.tsx | 35 ++-- .../main/js/components/controls/Tooltip.tsx | 11 +- .../components/facet/ListStyleFacetFooter.tsx | 3 +- .../components/issue/popups/CommentList.tsx | 56 ------ .../measure/RatingTooltipContent.tsx | 8 +- .../measure/__tests__/Measure-test.tsx | 19 +- .../components/upgrade/SystemUpgradeForm.tsx | 138 +++++++------- .../src/main/js/components/upgrade/utils.ts | 2 + .../js/helpers/__tests__/measures-test.ts | 177 ++++++++++-------- .../src/main/js/helpers/constants.ts | 2 + .../sonar-web/src/main/js/helpers/issues.ts | 8 +- .../sonar-web/src/main/js/helpers/measures.ts | 38 ++-- .../src/main/js/helpers/projectLinks.ts | 2 +- .../sonar-web/src/main/js/helpers/request.ts | 27 ++- server/sonar-web/src/main/js/types/issues.ts | 21 +++ 49 files changed, 570 insertions(+), 458 deletions(-) delete mode 100644 server/sonar-web/src/main/js/components/issue/popups/CommentList.tsx diff --git a/server/sonar-web/src/main/js/api/issues.ts b/server/sonar-web/src/main/js/api/issues.ts index fccb2b112f6..60c3fcd81da 100644 --- a/server/sonar-web/src/main/js/api/issues.ts +++ b/server/sonar-web/src/main/js/api/issues.ts @@ -28,29 +28,9 @@ import { postJSON, RequestData, } from '../helpers/request'; -import { IssueResponse, ListIssuesResponse, RawIssuesResponse } from '../types/issues'; +import { FacetName, IssueResponse, ListIssuesResponse, RawIssuesResponse } from '../types/issues'; import { Dict, FacetValue, IssueChangelog, SnippetsByComponent, SourceLine } from '../types/types'; -type FacetName = - | 'assigned_to_me' - | 'assignees' - | 'author' - | 'codeVariants' - | 'createdAt' - | 'cwe' - | 'directories' - | 'files' - | 'languages' - | 'owaspTop10' - | 'projects' - | 'reporters' - | 'resolutions' - | 'rules' - | 'severities' - | 'statuses' - | 'tags' - | 'types'; - export function searchIssues(query: RequestData): Promise { return getJSON('/api/issues/search', query).catch(throwGlobalError); } diff --git a/server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts index 4fd47633585..2b8241ebdcf 100644 --- a/server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts @@ -503,26 +503,63 @@ export default class CodingRulesServiceMock { rule: string; severity?: string; }) => { - const nextActivation = mockRuleActivation({ - qProfile: data.key, - severity: data.severity, - params: Object.entries(data.params ?? {}).map(([key, value]) => ({ key, value })), - }); + if (data.reset) { + const parentQP = this.qualityProfile.find((p) => p.key === data.key)?.parentKey!; + const parentActivation = this.rulesActivations[data.rule]?.find( + (activation) => activation.qProfile === parentQP, + )!; + const parentParams = parentActivation?.params ?? []; + const activation = this.rulesActivations[data.rule]?.find( + ({ qProfile }) => qProfile === data.key, + )!; + activation.inherit = 'INHERITED'; + activation.params = parentParams; + + return this.reply(undefined); + } + + const nextActivations = [ + mockRuleActivation({ + qProfile: data.key, + severity: data.severity, + params: Object.entries(data.params ?? {}).map(([key, value]) => ({ key, value })), + }), + ]; + + const inheritingProfiles = this.qualityProfile.filter( + (p) => p.isInherited && p.parentKey === data.key, + ); + nextActivations.push( + ...inheritingProfiles.map((profile) => + mockRuleActivation({ + qProfile: profile.key, + severity: data.severity, + inherit: 'INHERITED', + params: Object.entries(data.params ?? {}).map(([key, value]) => ({ key, value })), + }), + ), + ); if (!this.rulesActivations[data.rule]) { - this.rulesActivations[data.rule] = [nextActivation]; + this.rulesActivations[data.rule] = nextActivations; return this.reply(undefined); } - const activationIndex = this.rulesActivations[data.rule]?.findIndex((activation) => { - return activation.qProfile === data.key; + nextActivations.forEach((nextActivation) => { + const activationIndex = this.rulesActivations[data.rule]?.findIndex( + ({ qProfile }) => qProfile === nextActivation.qProfile, + ); + + if (activationIndex !== -1) { + this.rulesActivations[data.rule][activationIndex] = { + ...nextActivation, + inherit: 'OVERRIDES', + }; + } else { + this.rulesActivations[data.rule].push(nextActivation); + } }); - if (activationIndex !== -1) { - this.rulesActivations[data.rule][activationIndex] = nextActivation; - } else { - this.rulesActivations[data.rule].push(nextActivation); - } return this.reply(undefined); }; diff --git a/server/sonar-web/src/main/js/api/mocks/data/ids.ts b/server/sonar-web/src/main/js/api/mocks/data/ids.ts index 075d5a0ef08..f680b17e2a8 100644 --- a/server/sonar-web/src/main/js/api/mocks/data/ids.ts +++ b/server/sonar-web/src/main/js/api/mocks/data/ids.ts @@ -56,6 +56,7 @@ export const QP_1 = 'p1'; export const QP_2 = 'p2'; export const QP_3 = 'p3'; export const QP_4 = 'p4'; +export const QP_5 = 'p5'; // Issues. export const ISSUE_0 = 'issue0'; diff --git a/server/sonar-web/src/main/js/api/mocks/data/qualityProfiles.ts b/server/sonar-web/src/main/js/api/mocks/data/qualityProfiles.ts index de989317103..c7f584669e6 100644 --- a/server/sonar-web/src/main/js/api/mocks/data/qualityProfiles.ts +++ b/server/sonar-web/src/main/js/api/mocks/data/qualityProfiles.ts @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { mockQualityProfile } from '../../../helpers/testMocks'; -import { QP_1, QP_2, QP_3, QP_4 } from './ids'; +import { QP_1, QP_2, QP_3, QP_4, QP_5 } from './ids'; export function mockQualityProfilesList() { return [ @@ -37,5 +37,14 @@ export function mockQualityProfilesList() { language: 'java', languageName: 'Java', }), + mockQualityProfile({ + key: QP_5, + name: 'QP FooBaz', + language: 'java', + languageName: 'Java', + isInherited: true, + parentKey: QP_4, + parentName: 'QP FooBarBaz', + }), ]; } diff --git a/server/sonar-web/src/main/js/app/components/__tests__/RecentHistory-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/RecentHistory-test.tsx index 6d6823bf780..2f51251fe24 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/RecentHistory-test.tsx +++ b/server/sonar-web/src/main/js/app/components/__tests__/RecentHistory-test.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { get, remove, save } from '../../../helpers/storage'; +import { ComponentQualifier } from '../../../types/component'; import RecentHistory, { History } from '../RecentHistory'; jest.mock('../../../helpers/storage', () => ({ @@ -27,33 +28,33 @@ jest.mock('../../../helpers/storage', () => ({ })); beforeEach(() => { - (get as jest.Mock).mockClear(); - (remove as jest.Mock).mockClear(); - (save as jest.Mock).mockClear(); + jest.mocked(get).mockClear(); + jest.mocked(remove).mockClear(); + jest.mocked(save).mockClear(); }); it('should get existing history', () => { - const history = [{ key: 'foo', name: 'Foo', icon: 'TRK' }]; - (get as jest.Mock).mockReturnValueOnce(JSON.stringify(history)); + const history = [{ key: 'foo', name: 'Foo', icon: ComponentQualifier.Project }]; + jest.mocked(get).mockReturnValueOnce(JSON.stringify(history)); expect(RecentHistory.get()).toEqual(history); expect(get).toHaveBeenCalledWith('sonar_recent_history'); }); it('should get empty history', () => { - (get as jest.Mock).mockReturnValueOnce(null); + jest.mocked(get).mockReturnValueOnce(null); expect(RecentHistory.get()).toEqual([]); expect(get).toHaveBeenCalledWith('sonar_recent_history'); }); it('should return [] and clear history in case of failure', () => { - (get as jest.Mock).mockReturnValueOnce('not a json'); + jest.mocked(get).mockReturnValueOnce('not a json'); expect(RecentHistory.get()).toEqual([]); expect(get).toHaveBeenCalledWith('sonar_recent_history'); expect(remove).toHaveBeenCalledWith('sonar_recent_history'); }); it('should save history', () => { - const history = [{ key: 'foo', name: 'Foo', icon: 'TRK' }]; + const history = [{ key: 'foo', name: 'Foo', icon: ComponentQualifier.Project }]; RecentHistory.set(history); expect(save).toHaveBeenCalledWith('sonar_recent_history', JSON.stringify(history)); }); @@ -64,34 +65,37 @@ it('should clear history', () => { }); it('should add item to history', () => { - const history = [{ key: 'foo', name: 'Foo', icon: 'TRK' }]; - (get as jest.Mock).mockReturnValueOnce(JSON.stringify(history)); - RecentHistory.add('bar', 'Bar', 'VW'); + const history = [{ key: 'foo', name: 'Foo', icon: ComponentQualifier.Project }]; + jest.mocked(get).mockReturnValueOnce(JSON.stringify(history)); + RecentHistory.add('bar', 'Bar', ComponentQualifier.Portfolio); expect(save).toHaveBeenCalledWith( 'sonar_recent_history', - JSON.stringify([{ key: 'bar', name: 'Bar', icon: 'VW' }, ...history]), + JSON.stringify([{ key: 'bar', name: 'Bar', icon: ComponentQualifier.Portfolio }, ...history]), ); }); it('should keep 10 items maximum', () => { const history: History = []; for (let i = 0; i < 10; i++) { - history.push({ key: `key-${i}`, name: `name-${i}`, icon: 'TRK' }); + history.push({ key: `key-${i}`, name: `name-${i}`, icon: ComponentQualifier.Project }); } - (get as jest.Mock).mockReturnValueOnce(JSON.stringify(history)); - RecentHistory.add('bar', 'Bar', 'VW'); + jest.mocked(get).mockReturnValueOnce(JSON.stringify(history)); + RecentHistory.add('bar', 'Bar', ComponentQualifier.Portfolio); expect(save).toHaveBeenCalledWith( 'sonar_recent_history', - JSON.stringify([{ key: 'bar', name: 'Bar', icon: 'VW' }, ...history.slice(0, 9)]), + JSON.stringify([ + { key: 'bar', name: 'Bar', icon: ComponentQualifier.Portfolio }, + ...history.slice(0, 9), + ]), ); }); it('should remove component from history', () => { const history: History = []; for (let i = 0; i < 10; i++) { - history.push({ key: `key-${i}`, name: `name-${i}`, icon: 'TRK' }); + history.push({ key: `key-${i}`, name: `name-${i}`, icon: ComponentQualifier.Project }); } - (get as jest.Mock).mockReturnValueOnce(JSON.stringify(history)); + jest.mocked(get).mockReturnValueOnce(JSON.stringify(history)); RecentHistory.remove('key-5'); expect(save).toHaveBeenCalledWith( 'sonar_recent_history', diff --git a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationHelper.ts b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationHelper.ts index 2c330531e55..a25b4e74993 100644 --- a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationHelper.ts +++ b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationHelper.ts @@ -70,7 +70,7 @@ export default class IndexationNotificationHelper { static shouldDisplayCompletedNotification() { return JSON.parse( - get(LS_INDEXATION_COMPLETED_NOTIFICATION_SHOULD_BE_DISPLAYED) || false.toString(), + get(LS_INDEXATION_COMPLETED_NOTIFICATION_SHOULD_BE_DISPLAYED) ?? false.toString(), ); } } diff --git a/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx b/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx index 8d54909c6e0..158b1f3f397 100644 --- a/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx +++ b/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx @@ -23,7 +23,7 @@ import { getSystemUpgrades } from '../../../api/system'; import { Alert, AlertVariant } from '../../../components/ui/Alert'; import DismissableAlert from '../../../components/ui/DismissableAlert'; import SystemUpgradeButton from '../../../components/upgrade/SystemUpgradeButton'; -import { sortUpgrades, UpdateUseCase } from '../../../components/upgrade/utils'; +import { UpdateUseCase, sortUpgrades } from '../../../components/upgrade/utils'; import { translate } from '../../../helpers/l10n'; import { hasGlobalPermission } from '../../../helpers/users'; import { AppState } from '../../../types/appstate'; @@ -189,8 +189,8 @@ export class UpdateNotification extends React.PureComponent { const latest = [...upgrades].sort( (upgrade1, upgrade2) => - new Date(upgrade2.releaseDate || '').getTime() - - new Date(upgrade1.releaseDate || '').getTime(), + new Date(upgrade2.releaseDate ?? '').getTime() - + new Date(upgrade1.releaseDate ?? '').getTime(), )[0]; const dismissKey = useCase + latest.version; diff --git a/server/sonar-web/src/main/js/apps/background-tasks/utils.ts b/server/sonar-web/src/main/js/apps/background-tasks/utils.ts index 9007eafbb57..4a644b40d1d 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/utils.ts +++ b/server/sonar-web/src/main/js/apps/background-tasks/utils.ts @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { ONE_SECOND } from '../../helpers/constants'; import { toShortISO8601String } from '../../helpers/dates'; import { ActivityRequestParameters, Task, TaskStatuses } from '../../types/tasks'; import { ALL_TYPES, CURRENTS, STATUSES } from './constants'; @@ -79,7 +80,6 @@ export function mapFiltersToParameters(filters: Partial = {}) { return parameters; } -const ONE_SECOND = 1000; const ONE_MINUTE = 60 * ONE_SECOND; const ONE_HOUR = 60 * ONE_MINUTE; diff --git a/server/sonar-web/src/main/js/apps/code/__tests__/utils-test.tsx b/server/sonar-web/src/main/js/apps/code/__tests__/utils-test.tsx index fd751991b38..31f55f120e1 100644 --- a/server/sonar-web/src/main/js/apps/code/__tests__/utils-test.tsx +++ b/server/sonar-web/src/main/js/apps/code/__tests__/utils-test.tsx @@ -19,6 +19,7 @@ */ import { getBreadcrumbs, getChildren, getComponent } from '../../../api/components'; import { mockMainBranch, mockPullRequest } from '../../../helpers/mocks/branch-like'; +import { ComponentQualifier } from '../../../types/component'; import { addComponent, addComponentBreadcrumbs, @@ -54,23 +55,31 @@ beforeEach(() => { describe('getCodeMetrics', () => { it('should return the right metrics for portfolios', () => { - expect(getCodeMetrics('VW')).toMatchSnapshot(); - expect(getCodeMetrics('VW', undefined, { includeQGStatus: true })).toMatchSnapshot(); + expect(getCodeMetrics(ComponentQualifier.Portfolio)).toMatchSnapshot(); expect( - getCodeMetrics('VW', undefined, { includeQGStatus: true, newCode: true }), + getCodeMetrics(ComponentQualifier.Portfolio, undefined, { includeQGStatus: true }), ).toMatchSnapshot(); expect( - getCodeMetrics('VW', undefined, { includeQGStatus: true, newCode: false }), + getCodeMetrics(ComponentQualifier.Portfolio, undefined, { + includeQGStatus: true, + newCode: true, + }), + ).toMatchSnapshot(); + expect( + getCodeMetrics(ComponentQualifier.Portfolio, undefined, { + includeQGStatus: true, + newCode: false, + }), ).toMatchSnapshot(); }); it('should return the right metrics for apps', () => { - expect(getCodeMetrics('APP')).toMatchSnapshot(); + expect(getCodeMetrics(ComponentQualifier.Application)).toMatchSnapshot(); }); it('should return the right metrics for projects', () => { - expect(getCodeMetrics('TRK', mockMainBranch())).toMatchSnapshot(); - expect(getCodeMetrics('TRK', mockPullRequest())).toMatchSnapshot(); + expect(getCodeMetrics(ComponentQualifier.Project, mockMainBranch())).toMatchSnapshot(); + expect(getCodeMetrics(ComponentQualifier.Project, mockPullRequest())).toMatchSnapshot(); }); }); @@ -82,7 +91,12 @@ describe('retrieveComponentChildren', () => { paging: { total: 2, pageIndex: 0 }, }); - await retrieveComponentChildren('key', 'TRK', { mounted: true }, mockMainBranch()); + await retrieveComponentChildren( + 'key', + ComponentQualifier.Project, + { mounted: true }, + mockMainBranch(), + ); expect(addComponentChildren).toHaveBeenCalledWith('key', components, 2, 0); expect(addComponent).toHaveBeenCalledTimes(2); @@ -102,7 +116,7 @@ describe('retrieveComponent', () => { }); (getBreadcrumbs as jest.Mock).mockResolvedValueOnce([]); - await retrieveComponent('key', 'TRK', { mounted: true }, mockMainBranch()); + await retrieveComponent('key', ComponentQualifier.Project, { mounted: true }, mockMainBranch()); expect(addComponentChildren).toHaveBeenCalled(); expect(addComponent).toHaveBeenCalledTimes(3); @@ -120,7 +134,12 @@ describe('retrieveComponent', () => { }); (getBreadcrumbs as jest.Mock).mockResolvedValueOnce([]); - await retrieveComponent('key', 'TRK', { mounted: false }, mockMainBranch()); + await retrieveComponent( + 'key', + ComponentQualifier.Project, + { mounted: false }, + mockMainBranch(), + ); expect(addComponentChildren).not.toHaveBeenCalled(); expect(addComponent).not.toHaveBeenCalled(); @@ -136,7 +155,13 @@ describe('loadMoreChildren', () => { paging: { total: 6, pageIndex: 1 }, }); - await loadMoreChildren('key', 1, 'TRK', { mounted: true }, mockMainBranch()); + await loadMoreChildren( + 'key', + 1, + ComponentQualifier.Project, + { mounted: true }, + mockMainBranch(), + ); expect(addComponentChildren).toHaveBeenCalledWith('key', components, 6, 1); expect(addComponent).toHaveBeenCalledTimes(3); diff --git a/server/sonar-web/src/main/js/apps/code/utils.ts b/server/sonar-web/src/main/js/apps/code/utils.ts index f4668d1ed78..433f6bda39b 100644 --- a/server/sonar-web/src/main/js/apps/code/utils.ts +++ b/server/sonar-web/src/main/js/apps/code/utils.ts @@ -94,7 +94,7 @@ export function showLeakMeasure(branchLike?: BranchLike) { function skipRootDir(breadcrumbs: ComponentMeasure[]) { return breadcrumbs.filter((component) => { - return !(component.qualifier === 'DIR' && component.name === '/'); + return !(component.qualifier === ComponentQualifier.Directory && component.name === '/'); }); } @@ -189,7 +189,7 @@ export async function retrieveComponentChildren( if (instance.mounted && isPortfolioLike(qualifier)) { await Promise.all( - result.components.map((c) => getComponentData({ component: c.refKey || c.key })), + result.components.map((c) => getComponentData({ component: c.refKey ?? c.key })), ).then( (data) => { data.forEach(({ component: { analysisDate } }, i) => { diff --git a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts index 7258e03991b..c3be7edfde1 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts +++ b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts @@ -516,21 +516,16 @@ describe('Rule app details', () => { // Activate rule in quality profile await user.click(ui.activateButton.get()); await selectEvent.select(ui.qualityProfileSelect.get(), 'QP FooBar'); - await user.type(ui.paramInput('1').get(), 'paramInput'); await act(() => user.click(ui.activateButton.get(ui.activateQPDialog.get()))); expect(ui.qpLink('QP FooBar').get()).toBeInTheDocument(); - // Change rule details in quality profile - await user.click(ui.changeButton('QP FooBar').get()); - await user.type(ui.paramInput('1').get(), 'New'); - await act(() => user.click(ui.saveButton.get(ui.changeQPDialog.get()))); - expect(screen.getByText('paramInputNew')).toBeInTheDocument(); - // activate last java rule await user.click(ui.activateButton.get()); + await user.type(ui.paramInput('1').get(), 'paramInput'); await act(() => user.click(ui.activateButton.get(ui.activateQPDialog.get()))); - expect(ui.qpLink('QP FooBarBaz').get()).toBeInTheDocument(); + expect(ui.qpLink('QP FooBarBaz').getAll()).toHaveLength(2); + expect(ui.qpLink('QP FooBaz').get()).toBeInTheDocument(); // Rule is activated in all quality profiles - show notification in dialog await user.click(ui.activateButton.get()); @@ -538,6 +533,17 @@ describe('Rule app details', () => { expect(ui.activateButton.get(ui.activateQPDialog.get())).toBeDisabled(); await user.click(ui.cancelButton.get()); + // Change rule details in quality profile + await user.click(ui.changeButton('QP FooBaz').get()); + await user.type(ui.paramInput('1').get(), 'New'); + await act(() => user.click(ui.saveButton.get(ui.changeQPDialog.get()))); + expect(screen.getByText('paramInputNew')).toBeInTheDocument(); + + // Revert rule details in quality profile + await user.click(ui.revertToParentDefinitionButton.get()); + await act(() => user.click(ui.yesButton.get())); + expect(screen.queryByText('paramInputNew')).not.toBeInTheDocument(); + // Deactivate rule in quality profile await user.click(ui.deactivateInQPButton('QP FooBar').get()); await act(() => user.click(ui.yesButton.get())); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChange.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChange.tsx index 710bca3d4ff..9d8b36aed71 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChange.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChange.tsx @@ -19,9 +19,9 @@ */ import * as React from 'react'; import { Profile } from '../../../api/quality-profiles'; -import { Button } from '../../../components/controls/buttons'; import Dropdown from '../../../components/controls/Dropdown'; import Tooltip from '../../../components/controls/Tooltip'; +import { Button } from '../../../components/controls/buttons'; import { PopupPlacement } from '../../../components/ui/popups'; import { translate } from '../../../helpers/l10n'; import { Dict } from '../../../types/types'; @@ -77,7 +77,7 @@ export default class BulkChange extends React.PureComponent { render() { // show "Bulk Change" button only if user is admin of at least one QP const canBulkChange = Object.values(this.props.referencedProfiles).some((profile) => - Boolean(profile.actions && profile.actions.edit), + Boolean(profile.actions?.edit), ); if (!canBulkChange) { return ( diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx index f231e61eb1c..65e6d4fee4d 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx @@ -27,6 +27,7 @@ import { Alert } from '../../../components/ui/Alert'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { formatMeasure } from '../../../helpers/measures'; import { Languages } from '../../../types/languages'; +import { MetricType } from '../../../types/metrics'; import { Dict } from '../../../types/types'; import { Query, serializeQuery } from '../query'; @@ -98,7 +99,7 @@ export class BulkChangeModal extends React.PureComponent { profiles = profiles.filter((profile) => query.languages.includes(profile.language)); } return profiles - .filter((profile) => profile.actions && profile.actions.edit) + .filter((profile) => profile.actions?.edit) .filter((profile) => !profile.isBuiltIn); }; @@ -213,11 +214,11 @@ export class BulkChangeModal extends React.PureComponent { action === 'activate' ? `${translate('coding_rules.activate_in_quality_profile')} (${formatMeasure( total, - 'INT', + MetricType.Integer, )} ${translate('coding_rules._rules')})` : `${translate('coding_rules.deactivate_in_quality_profile')} (${formatMeasure( total, - 'INT', + MetricType.Integer, )} ${translate('coding_rules._rules')})`; return ( diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx index 8ff95a87041..874ed9da3c1 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx @@ -29,6 +29,8 @@ import { translate } from '../../../helpers/l10n'; import { formatMeasure } from '../../../helpers/measures'; import { getIssuesUrl } from '../../../helpers/urls'; import { Feature } from '../../../types/features'; +import { FacetName } from '../../../types/issues'; +import { MetricType } from '../../../types/metrics'; import { RuleDetails } from '../../../types/types'; interface Props extends WithAvailableFeaturesProps { @@ -77,7 +79,7 @@ export class RuleDetailsIssues extends React.PureComponent { resolved: 'false', rules: key, }, - 'projects', + FacetName.Projects, ).then( ({ facet, response }) => { if (this.mounted) { @@ -138,7 +140,7 @@ export class RuleDetailsIssues extends React.PureComponent { {project.name} - {formatMeasure(project.count, 'INT')} + {formatMeasure(project.count, MetricType.Integer)} ); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx index f11ac3f8d4e..e72f318e7df 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx @@ -43,7 +43,7 @@ export default class RuleDetailsProfiles extends React.PureComponent { handleActivate = () => this.props.onActivate(); handleDeactivate = (key?: string) => { - if (key) { + if (key !== undefined) { deactivateRule({ key, rule: this.props.ruleDetails.key, @@ -52,7 +52,7 @@ export default class RuleDetailsProfiles extends React.PureComponent { }; handleRevert = (key?: string) => { - if (key) { + if (key !== undefined) { activateRule({ key, rule: this.props.ruleDetails.key, @@ -81,9 +81,8 @@ export default class RuleDetailsProfiles extends React.PureComponent { }; renderParameter = (param: { key: string; value: string }, parentActivation?: RuleActivation) => { - const originalParam = - parentActivation && parentActivation.params.find((p) => p.key === param.key); - const originalValue = originalParam && originalParam.value; + const originalParam = parentActivation?.params.find((p) => p.key === param.key); + const originalValue = originalParam?.value; return (
@@ -108,7 +107,7 @@ export default class RuleDetailsProfiles extends React.PureComponent { ); renderActions = (activation: RuleActivation, profile: Profile) => { - const canEdit = profile.actions && profile.actions.edit && !profile.isBuiltIn; + const canEdit = profile.actions?.edit && !profile.isBuiltIn; const { ruleDetails } = this.props; const hasParent = activation.inherit !== 'NONE' && profile.parentKey; return ( @@ -204,7 +203,7 @@ export default class RuleDetailsProfiles extends React.PureComponent { render() { const { activations = [], referencedProfiles, ruleDetails } = this.props; const canActivate = Object.values(referencedProfiles).some((profile) => - Boolean(profile.actions && profile.actions.edit && profile.language === ruleDetails.lang), + Boolean(profile.actions?.edit && profile.language === ruleDetails.lang), ); return ( diff --git a/server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx b/server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx index dd518f5c590..7af8515d6b0 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx @@ -142,6 +142,9 @@ const selectors = { changeQPDialog: byRole('dialog', { name: 'coding_rules.change_details' }), deactivateInQPButton: (profile: string) => byRole('button', { name: `coding_rules.deactivate_in_quality_profile_x.${profile}` }), + revertToParentDefinitionButton: byRole('button', { + name: 'coding_rules.revert_to_parent_definition', + }), activaInAllQPs: byText('coding_rules.active_in_all_profiles'), yesButton: byRole('button', { name: 'yes' }), paramInput: (param: string) => byRole('textbox', { name: param }), diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx index 1778b6336fd..91703ee82b2 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx @@ -22,12 +22,26 @@ import { screen } from '@testing-library/react'; import * as React from 'react'; import { mockComponent } from '../../../../helpers/mocks/component'; import { mockQuery } from '../../../../helpers/mocks/issues'; +import { + renderOwaspTop102021Category, + renderOwaspTop10Category, + renderSonarSourceSecurityCategory, +} from '../../../../helpers/security-standard'; import { mockAppState } from '../../../../helpers/testMocks'; import { renderComponent } from '../../../../helpers/testReactTestingUtils'; import { ComponentQualifier } from '../../../../types/component'; import { GlobalSettingKeys } from '../../../../types/settings'; import { SidebarClass as Sidebar } from '../Sidebar'; +jest.mock('../../../../helpers/security-standard', () => { + return { + ...jest.requireActual('../../../../helpers/security-standard'), + renderOwaspTop10Category: jest.fn(), + renderOwaspTop102021Category: jest.fn(), + renderSonarSourceSecurityCategory: jest.fn(), + }; +}); + it('should render correct facets for Application', () => { renderSidebar({ component: mockComponent({ qualifier: ComponentQualifier.Application }) }); @@ -113,6 +127,22 @@ it.each([ expect(screen.getByText(text)).toBeInTheDocument(); }); +it('should call functions from security-standard', () => { + renderSidebar({ + component: mockComponent({ qualifier: ComponentQualifier.Application }), + query: { + ...mockQuery(), + owaspTop10: ['foo'], + 'owaspTop10-2021': ['bar'], + sonarsourceSecurity: ['baz'], + }, + }); + + expect(renderOwaspTop10Category).toHaveBeenCalledTimes(1); + expect(renderOwaspTop102021Category).toHaveBeenCalledTimes(1); + expect(renderSonarSourceSecurityCategory).toHaveBeenCalledTimes(1); +}); + function renderSidebar(props: Partial = {}) { return renderComponent( props.onUpdateSelectedDate(analysis.date)} ref={(ref) => (node = ref)} @@ -145,7 +145,7 @@ function ProjectActivityAnalysis(props: ProjectActivityAnalysisProps) { , ) { return ( - branch.newCodePeriod && - branch.newCodePeriod.value && + branch.newCodePeriod?.value && branch.newCodePeriod.type === NewCodeDefinitionType.ReferenceBranch && !existingBranches.includes(branch.newCodePeriod.value) ); @@ -95,7 +94,7 @@ export default function BranchListRow(props: BranchListRowProps) { } else if (referenceBranchDoesNotExist(branch, existingBranches)) { settingWarning = translateWithParameters( 'baseline.reference_branch.does_not_exist', - branch.newCodePeriod?.value || '', + branch.newCodePeriod?.value ?? '', ); } @@ -113,7 +112,7 @@ export default function BranchListRow(props: BranchListRowProps) { - {settingWarning && } + {settingWarning !== undefined && } {branch.newCodePeriod ? renderNewCodePeriodSetting(branch.newCodePeriod) : translate('branch_list.default_setting')} diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx index ee7b7b00ff9..b527f81c962 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx @@ -66,7 +66,7 @@ export class ProjectQualityProfilesApp extends React.PureComponent checkPermissions() { const { configuration } = this.props.component; - const hasPermission = configuration && configuration.showQualityProfiles; + const hasPermission = configuration?.showQualityProfiles; return !!hasPermission; } @@ -121,25 +121,26 @@ export class ProjectQualityProfilesApp extends React.PureComponent // If the profile is the default profile, all is good. if (profile.isDefault) { return { profile, selected: false }; - } else { - // If it is neither the default, nor explicitly selected, it - // means this is outdated information. This can only mean the - // user wants to use the default profile, but it will only - // be taken into account after a new analysis. Fetch the - // default profile. - const defaultProfile = allProfiles.find( - (p) => p.isDefault && p.language === profile.language, - ); - return ( - defaultProfile && { - profile: defaultProfile, - selected: false, - } - ); } - } else { - return undefined; + + // If it is neither the default, nor explicitly selected, it + // means this is outdated information. This can only mean the + // user wants to use the default profile, but it will only + // be taken into account after a new analysis. Fetch the + // default profile. + const defaultProfile = allProfiles.find( + (p) => p.isDefault && p.language === profile.language, + ); + + return ( + defaultProfile && { + profile: defaultProfile, + selected: false, + } + ); } + + return undefined; }) .filter(isDefined); @@ -205,7 +206,7 @@ export class ProjectQualityProfilesApp extends React.PureComponent const { component } = this.props; const { allProfiles = [], projectProfiles = [] } = this.state; - const newProfile = newKey && allProfiles.find((p) => p.key === newKey); + const newProfile = newKey !== undefined && allProfiles.find((p) => p.key === newKey); const oldProjectProfile = projectProfiles.find((p) => p.profile.key === oldKey); const defaultProfile = allProfiles.find( (p) => p.isDefault && p.language === oldProjectProfile?.profile.language, diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx index 316e48c441f..6e83258278e 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx @@ -35,26 +35,31 @@ export type LanguageProfileSelectOptionProps = OptionProps export default function LanguageProfileSelectOption(props: LanguageProfileSelectOptionProps) { const option = props.data; + const SelectOptionDisableTooltipOverlay = React.useCallback( + () => ( + <> +

+ {translate( + 'project_quality_profile.add_language_modal.profile_unavailable_no_active_rules', + )} +

+ {option.label && option.language && ( + + {translate('project_quality_profile.add_language_modal.go_to_profile')} + + )} + + ), + [option.label, option.language], + ); + return (
( - <> -

- {translate( - 'project_quality_profile.add_language_modal.profile_unavailable_no_active_rules', - )} -

- {option.label && option.language && ( - - {translate('project_quality_profile.add_language_modal.go_to_profile')} - - )} - - )} + disableTooltipOverlay={SelectOptionDisableTooltipOverlay} />
diff --git a/server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts index 0004e2e6edb..a15e056eefd 100644 --- a/server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts @@ -19,6 +19,7 @@ */ import { searchProjects } from '../../../api/components'; +import { ONE_SECOND } from '../../../helpers/constants'; import { mockComponent } from '../../../helpers/mocks/component'; import { Component } from '../../../types/types'; import * as utils from '../utils'; @@ -58,7 +59,6 @@ describe('parseSorting', () => { }); describe('formatDuration', () => { - const ONE_SECOND = 1000; const ONE_MINUTE = 60 * ONE_SECOND; const ONE_HOUR = 60 * ONE_MINUTE; const ONE_DAY = 24 * ONE_HOUR; @@ -102,11 +102,21 @@ describe('fetchProjects', () => { ps: 50, }); - await utils.fetchProjects({ isFavorite: false, pageIndex: 3, query: { view: 'leak' } }); + await utils.fetchProjects({ + isFavorite: false, + pageIndex: 3, + query: { + view: 'leak', + new_reliability: 6, + incorrect_property: 'should not appear in post data', + search: 'foo', + }, + }); expect(searchProjects).toHaveBeenCalledWith({ f: 'analysisDate,leakPeriodDate', facets: utils.LEAK_FACETS.join(), + filter: 'new_reliability_rating = 6 and query = "foo"', p: 3, ps: 50, }); diff --git a/server/sonar-web/src/main/js/apps/projects/query.ts b/server/sonar-web/src/main/js/apps/projects/query.ts index 4cd3b4936b6..66317147971 100644 --- a/server/sonar-web/src/main/js/apps/projects/query.ts +++ b/server/sonar-web/src/main/js/apps/projects/query.ts @@ -148,14 +148,14 @@ function getAsLevel(value: any): Level | undefined { } function getAsString(value: any): string | undefined { - if (typeof value !== 'string' || !value) { + if (typeof value !== 'string' || value === '') { return undefined; } return value; } function getAsStringArray(value: any): string[] | undefined { - if (typeof value !== 'string' || !value) { + if (typeof value !== 'string' || value === '') { return undefined; } return value.split(','); @@ -172,9 +172,9 @@ function getView(value: any): string | undefined { function convertIssuesRating(metric: string, rating: number): string { if (rating > 1 && rating < 5) { return `${metric} >= ${rating}`; - } else { - return `${metric} = ${rating}`; } + + return `${metric} = ${rating}`; } function convertCoverage(metric: string, coverage: number): string { @@ -262,10 +262,10 @@ function pushMetricToArray( query: Query, property: string, conditionsArray: string[], - convertFunction: (metric: string, value: any) => string, + convertFunction: (metric: string, value: Query[string]) => string, ): void { const metric = mapPropertyToMetric(property); - if (query[property] != null && metric) { + if (query[property] !== undefined && metric !== undefined) { conditionsArray.push(convertFunction(metric, query[property])); } } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/quality-gates/__tests__/utils-test.ts index d00dd4a480a..efff71dbfa0 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/apps/quality-gates/__tests__/utils-test.ts @@ -39,10 +39,10 @@ const METRICS = { describe('getLocalizedMetricNameNoDiffMetric', () => { it('should return the correct corresponding metric', () => { - expect(getLocalizedMetricNameNoDiffMetric(mockMetric(), {})).toBe('coverage'); - expect(getLocalizedMetricNameNoDiffMetric(mockMetric({ key: 'new_bugs' }), METRICS)).toBe( - 'Bugs', - ); + expect(getLocalizedMetricNameNoDiffMetric(mockMetric(), {})).toBe(MetricKey.coverage); + expect( + getLocalizedMetricNameNoDiffMetric(mockMetric({ key: MetricKey.new_bugs }), METRICS), + ).toBe('Bugs'); expect( getLocalizedMetricNameNoDiffMetric( mockMetric({ key: 'new_custom_metric', name: 'Custom Metric on New Code' }), @@ -51,7 +51,7 @@ describe('getLocalizedMetricNameNoDiffMetric', () => { ).toBe('Custom Metric on New Code'); expect( getLocalizedMetricNameNoDiffMetric( - mockMetric({ key: 'new_maintainability_rating' }), + mockMetric({ key: MetricKey.new_maintainability_rating }), METRICS, ), ).toBe('Maintainability Rating'); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionReviewAndUpdateModal.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionReviewAndUpdateModal.tsx index d584a8d94c0..ed21ba444d9 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionReviewAndUpdateModal.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionReviewAndUpdateModal.tsx @@ -86,12 +86,12 @@ export default class CaycReviewUpdateConditionsModal extends React.PureComponent const { weakConditions, missingConditions } = getWeakMissingAndNonCaycConditions(conditions); const sortedWeakConditions = sortBy( weakConditions, - (condition) => metrics[condition.metric] && metrics[condition.metric].name, + (condition) => metrics[condition.metric]?.name, ); const sortedMissingConditions = sortBy( missingConditions, - (condition) => metrics[condition.metric] && metrics[condition.metric].name, + (condition) => metrics[condition.metric]?.name, ); return ( diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValueDescription.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValueDescription.tsx index 73718281fe4..1f330c16767 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValueDescription.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValueDescription.tsx @@ -22,11 +22,12 @@ import withAppStateContext from '../../../app/components/app-state/withAppStateC import { translate, translateWithParameters } from '../../../helpers/l10n'; import { formatMeasure } from '../../../helpers/measures'; import { - getMaintainabilityGrid, GRID_INDEX_OFFSET, PERCENT_MULTIPLIER, + getMaintainabilityGrid, } from '../../../helpers/ratings'; import { AppState } from '../../../types/appstate'; +import { MetricKey, MetricType } from '../../../types/metrics'; import { GlobalSettingKeys } from '../../../types/settings'; import { Condition, Metric } from '../../../types/types'; import { isCaycCondition } from '../utils'; @@ -50,13 +51,13 @@ function ConditionValueDescription({ metric, className = '', }: Props) { - if (condition.metric === 'new_maintainability_rating') { + if (condition.metric === MetricKey.new_maintainability_rating) { const maintainabilityGrid = getMaintainabilityGrid( settings[GlobalSettingKeys.RatingGrid] ?? '', ); const maintainabilityRatingThreshold = maintainabilityGrid[Math.floor(Number(condition.error)) - GRID_INDEX_OFFSET]; - const ratingLetter = formatMeasure(condition.error, 'RATING'); + const ratingLetter = formatMeasure(condition.error, MetricType.Rating); return ( @@ -64,12 +65,15 @@ function ConditionValueDescription({ {condition.error === '1' ? translateWithParameters( 'quality_gates.cayc.new_maintainability_rating.A', - formatMeasure(maintainabilityGrid[0] * PERCENT_MULTIPLIER, 'PERCENT'), + formatMeasure(maintainabilityGrid[0] * PERCENT_MULTIPLIER, MetricType.Percent), ) : translateWithParameters( 'quality_gates.cayc.new_maintainability_rating', ratingLetter, - formatMeasure(maintainabilityRatingThreshold * PERCENT_MULTIPLIER, 'PERCENT'), + formatMeasure( + maintainabilityRatingThreshold * PERCENT_MULTIPLIER, + MetricType.Percent, + ), )} ) diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx index f9437cb79e9..abe159f8959 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx @@ -25,13 +25,14 @@ import { toShortISO8601String } from '../../../helpers/dates'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { formatMeasure } from '../../../helpers/measures'; import { getRulesUrl } from '../../../helpers/urls'; +import { MetricType } from '../../../types/metrics'; import { Dict, Rule, RuleActivation } from '../../../types/types'; const RULES_LIMIT = 10; function parseRules(rules: Rule[], actives?: Dict): ExtendedRule[] { return rules.map((rule) => { - const activations = actives && actives[rule.key]; + const activations = actives?.[rule.key]; return { ...rule, activations: activations ? activations.length : 0 }; }); } @@ -101,8 +102,7 @@ export default class EvolutionRules extends React.PureComponent<{}, State> { const newRulesUrl = getRulesUrl({ available_since: this.periodStartDate }); const seeAllRulesText = `${translate('see_all')} ${formatMeasure( latestRulesTotal, - 'SHORT_INT', - null, + MetricType.ShortInteger, )}`; return ( diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/utils.ts b/server/sonar-web/src/main/js/apps/security-hotspots/utils.ts index 10be89523b4..692aa128a90 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/utils.ts +++ b/server/sonar-web/src/main/js/apps/security-hotspots/utils.ts @@ -253,8 +253,8 @@ export function getLocations(rawFlows: RawHotspot['flows'], selectedFlowIndex: n function orderLocations(locations: FlowLocation[]) { return sortBy( locations, - (location) => location.textRange && location.textRange.startLine, - (location) => location.textRange && location.textRange.startOffset, + (location) => location.textRange?.startLine, + (location) => location.textRange?.startOffset, ); } diff --git a/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx b/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx index 81751fd980b..424281a6dd1 100644 --- a/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx +++ b/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx @@ -88,7 +88,7 @@ export class WebApiApp extends React.PureComponent { return domains.map((domain) => { const deprecated = getLatestDeprecatedAction(domain); const internal = !domain.actions.find((action: any) => !action.internal); - return { ...domain, deprecatedSince: deprecated && deprecated.deprecatedSince, internal }; + return { ...domain, deprecatedSince: deprecated?.deprecatedSince, internal }; }); } @@ -115,9 +115,9 @@ export class WebApiApp extends React.PureComponent { const action = domain.actions.find( (action) => getActionKey(domain.path, action.key) === splat, ); - const internal = Boolean(!query.internal && (domain.internal || (action && action.internal))); + const internal = Boolean(!query.internal && (domain.internal || action?.internal)); const deprecated = Boolean( - !query.deprecated && (domain.deprecatedSince || (action && action.deprecatedSince)), + !query.deprecated && (domain.deprecatedSince || action?.deprecatedSince), ); if (internal || deprecated) { this.updateQuery({ internal, deprecated }); @@ -136,7 +136,7 @@ export class WebApiApp extends React.PureComponent { const query = parseQuery(this.props.location.query); const value = !query[flag]; - if (domain && domain[domainFlag] && !value) { + if (domain?.[domainFlag] && !value) { this.props.router.push({ pathname: '/web_api', query: serializeQuery({ ...query, [flag]: false }), diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx index 2e820ece5d5..7ce331f09b6 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx @@ -38,7 +38,7 @@ export default function DeliveryItem({ className, delivery, loading, payload }:

{translateWithParameters( 'webhooks.delivery.response_x', - delivery.httpStatus || translate('webhooks.delivery.server_unreachable'), + delivery.httpStatus ?? translate('webhooks.delivery.server_unreachable'), )}

@@ -49,7 +49,7 @@ export default function DeliveryItem({ className, delivery, loading, payload }:

{translate('webhooks.delivery.payload')}

- {payload && } + {payload !== undefined && }
); diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx index 5264464bbb5..d3f489c5010 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx @@ -23,8 +23,8 @@ import { getFacets } from '../../../api/issues'; import { getMeasures } from '../../../api/measures'; import { getAllMetrics } from '../../../api/metrics'; import Link from '../../../components/common/Link'; -import { ResetButtonLink } from '../../../components/controls/buttons'; import Modal from '../../../components/controls/Modal'; +import { ResetButtonLink } from '../../../components/controls/buttons'; import IssueTypeIcon from '../../../components/icons/IssueTypeIcon'; import QualifierIcon from '../../../components/icons/QualifierIcon'; import TagsIcon from '../../../components/icons/TagsIcon'; @@ -39,7 +39,8 @@ import { } from '../../../helpers/measures'; import { getBranchLikeUrl } from '../../../helpers/urls'; import { BranchLike } from '../../../types/branch-like'; -import { IssueSeverity, IssueType as IssueTypeEnum } from '../../../types/issues'; +import { ComponentQualifier } from '../../../types/component'; +import { FacetName, IssueSeverity, IssueType as IssueTypeEnum } from '../../../types/issues'; import { MetricType } from '../../../types/metrics'; import { FacetValue, IssueType, MeasureEnhanced, SourceViewerFile } from '../../../types/types'; import Measure from '../../measure/Measure'; @@ -119,14 +120,14 @@ export default class MeasuresOverlay extends React.PureComponent { resolved: 'false', ...getBranchLikeQuery(this.props.branchLike), }, - ['types', 'severities', 'tags'], + [FacetName.Types, FacetName.Severities, FacetName.Tags], ).then(({ facets }) => { const severitiesFacet = facets.find((f) => f.property === 'severities'); const tagsFacet = facets.find((f) => f.property === 'tags'); const typesFacet = facets.find((f) => f.property === 'types'); return { - severitiesFacet: severitiesFacet && severitiesFacet.values, - tagsFacet: tagsFacet && tagsFacet.values, + severitiesFacet: severitiesFacet?.values, + tagsFacet: tagsFacet?.values, typesFacet: typesFacet && (typesFacet.values as FacetValue[]), }; }); @@ -386,7 +387,10 @@ export default class MeasuresOverlay extends React.PureComponent {
- + {sourceViewerFile.projectName} @@ -402,7 +406,7 @@ export default class MeasuresOverlay extends React.PureComponent { ) : ( <> - {sourceViewerFile.q === 'UTS' ? ( + {sourceViewerFile.q === ComponentQualifier.TestFile ? ( this.renderTests() ) : (
diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/ActivityGraph-it.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/ActivityGraph-it.tsx index 6a366358dc4..68bf691e373 100644 --- a/server/sonar-web/src/main/js/components/activity-graph/__tests__/ActivityGraph-it.tsx +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/ActivityGraph-it.tsx @@ -27,7 +27,7 @@ import { mockMetric } from '../../../helpers/testMocks'; import { renderComponent } from '../../../helpers/testReactTestingUtils'; import { byLabelText, byPlaceholderText, byRole, byText } from '../../../helpers/testSelector'; import { ComponentPropsType } from '../../../helpers/testUtils'; -import { MetricKey } from '../../../types/metrics'; +import { MetricKey, MetricType } from '../../../types/metrics'; import { GraphType, MeasureHistory } from '../../../types/project-activity'; import { Metric } from '../../../types/types'; import GraphsHeader from '../GraphsHeader'; @@ -271,19 +271,22 @@ function renderActivityGraph( metrics.push( mockMetric({ key: metric, - type: metric.includes('_density') || metric === MetricKey.coverage ? 'PERCENT' : 'INT', + type: + metric.includes('_density') || metric === MetricKey.coverage + ? MetricType.Percent + : MetricType.Integer, }), ); }); // The following should be filtered out, and not be suggested as options. metrics.push( - mockMetric({ key: MetricKey.new_bugs, type: 'INT' }), - mockMetric({ key: MetricKey.burned_budget, type: 'DATA' }), + mockMetric({ key: MetricKey.new_bugs, type: MetricType.Integer }), + mockMetric({ key: MetricKey.burned_budget, type: MetricType.Data }), ); // The following will not be filtered out, but has no values. - metrics.push(mockMetric({ key: MetricKey.test_failures, type: 'INT' })); + metrics.push(mockMetric({ key: MetricKey.test_failures, type: MetricType.Integer })); measuresHistory.push( mockMeasureHistory({ metric: MetricKey.test_failures, diff --git a/server/sonar-web/src/main/js/components/controls/ComponentReportActions.tsx b/server/sonar-web/src/main/js/components/controls/ComponentReportActions.tsx index d6265830068..d06e3d69aca 100644 --- a/server/sonar-web/src/main/js/components/controls/ComponentReportActions.tsx +++ b/server/sonar-web/src/main/js/components/controls/ComponentReportActions.tsx @@ -82,7 +82,7 @@ export class ComponentReportActions extends React.PureComponent { : 'component_report.unsubscribe_x_success'; const frequencyTranslation = translate( 'report.frequency', - status?.componentFrequency || status?.globalFrequency || '', + status?.componentFrequency ?? status?.globalFrequency ?? '', ).toLowerCase(); const qualifierTranslation = translate('qualifier', component.qualifier).toLowerCase(); diff --git a/server/sonar-web/src/main/js/components/controls/ListFooter.tsx b/server/sonar-web/src/main/js/components/controls/ListFooter.tsx index 40c7be0741d..2906d74a437 100644 --- a/server/sonar-web/src/main/js/components/controls/ListFooter.tsx +++ b/server/sonar-web/src/main/js/components/controls/ListFooter.tsx @@ -115,10 +115,10 @@ export default function ListFooter(props: ListFooterProps) { {total !== undefined ? translateWithParameters( 'x_of_y_shown', - formatMeasure(count, MetricType.Integer, null), - formatMeasure(total, MetricType.Integer, null), + formatMeasure(count, MetricType.Integer), + formatMeasure(total, MetricType.Integer), ) - : translateWithParameters('x_show', formatMeasure(count, MetricType.Integer, null))} + : translateWithParameters('x_show', formatMeasure(count, MetricType.Integer))} {button} {/* eslint-disable local-rules/no-conditional-rendering-of-deferredspinner */} diff --git a/server/sonar-web/src/main/js/components/controls/Select.tsx b/server/sonar-web/src/main/js/components/controls/Select.tsx index 0a519dd212c..aa2dc061402 100644 --- a/server/sonar-web/src/main/js/components/controls/Select.tsx +++ b/server/sonar-web/src/main/js/components/controls/Select.tsx @@ -93,28 +93,25 @@ export function multiValueRemove< return ×; } -/* Keeping it as a class to simplify a dozen tests */ -export default class Select< +export default function Select< Option = LabelValueSelectOption, IsMulti extends boolean = boolean, Group extends GroupBase