]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20327 Fix code smells following prettier upgrades
authorAmbroise C <ambroise.christea@sonarsource.com>
Wed, 13 Sep 2023 15:24:05 +0000 (17:24 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 13 Sep 2023 20:02:56 +0000 (20:02 +0000)
Co-authored-by: David Cho-Lerat <david.cho-lerat@sonarsource.com>
49 files changed:
server/sonar-web/src/main/js/api/issues.ts
server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts
server/sonar-web/src/main/js/api/mocks/data/ids.ts
server/sonar-web/src/main/js/api/mocks/data/qualityProfiles.ts
server/sonar-web/src/main/js/app/components/__tests__/RecentHistory-test.tsx
server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationHelper.ts
server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx
server/sonar-web/src/main/js/apps/background-tasks/utils.ts
server/sonar-web/src/main/js/apps/code/__tests__/utils-test.tsx
server/sonar-web/src/main/js/apps/code/utils.ts
server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts
server/sonar-web/src/main/js/apps/coding-rules/components/BulkChange.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx
server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.tsx
server/sonar-web/src/main/js/apps/projectNewCode/components/BranchListRow.tsx
server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx
server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx
server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts
server/sonar-web/src/main/js/apps/projects/query.ts
server/sonar-web/src/main/js/apps/quality-gates/__tests__/utils-test.ts
server/sonar-web/src/main/js/apps/quality-gates/components/ConditionReviewAndUpdateModal.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValueDescription.tsx
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx
server/sonar-web/src/main/js/apps/security-hotspots/utils.ts
server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx
server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx
server/sonar-web/src/main/js/components/activity-graph/__tests__/ActivityGraph-it.tsx
server/sonar-web/src/main/js/components/controls/ComponentReportActions.tsx
server/sonar-web/src/main/js/components/controls/ListFooter.tsx
server/sonar-web/src/main/js/components/controls/Select.tsx
server/sonar-web/src/main/js/components/controls/Tooltip.tsx
server/sonar-web/src/main/js/components/facet/ListStyleFacetFooter.tsx
server/sonar-web/src/main/js/components/issue/popups/CommentList.tsx [deleted file]
server/sonar-web/src/main/js/components/measure/RatingTooltipContent.tsx
server/sonar-web/src/main/js/components/measure/__tests__/Measure-test.tsx
server/sonar-web/src/main/js/components/upgrade/SystemUpgradeForm.tsx
server/sonar-web/src/main/js/components/upgrade/utils.ts
server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts
server/sonar-web/src/main/js/helpers/constants.ts
server/sonar-web/src/main/js/helpers/issues.ts
server/sonar-web/src/main/js/helpers/measures.ts
server/sonar-web/src/main/js/helpers/projectLinks.ts
server/sonar-web/src/main/js/helpers/request.ts
server/sonar-web/src/main/js/types/issues.ts

index fccb2b112f6a4555098cd2d78c141b8f44313c0a..60c3fcd81da36c987d3969a15493a316c00ce6db 100644 (file)
@@ -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<RawIssuesResponse> {
   return getJSON('/api/issues/search', query).catch(throwGlobalError);
 }
index 4fd47633585be93775e524dd236c211926aa3dab..2b8241ebdcf9c29ebe09a178b8596418589772b8 100644 (file)
@@ -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);
   };
 
index 075d5a0ef08303c191dc0f0a69556cdb1871f488..f680b17e2a83fae179f4ad7cc1cb498fd481746a 100644 (file)
@@ -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';
index de989317103a995a12ab75d7c352be1172473251..c7f584669e69c7b14a82722921656e9f065f40e4 100644 (file)
@@ -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',
+    }),
   ];
 }
index 6d6823bf780e5ffa63ac5fca8c926ff090646c88..2f51251fe24e09f3acf359a3560ba7901173cc4c 100644 (file)
@@ -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',
index 2c330531e55533f0b32b18b0f9ddc2ade2dddb78..a25b4e749933b0402a2a7c0b22b3309976ae489b 100644 (file)
@@ -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(),
     );
   }
 }
index 8d54909c6e0ed58aa641cacfcb9676a0c2e3a41e..158b1f3f39736567d4e6d04264303519fe6b379e 100644 (file)
@@ -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<Props, State> {
 
     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;
index 9007eafbb57963422554819f7ec7c750ff8d2cf5..4a644b40d1d17f07adc2b677fc42ec64d2663922 100644 (file)
@@ -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<Query> = {}) {
   return parameters;
 }
 
-const ONE_SECOND = 1000;
 const ONE_MINUTE = 60 * ONE_SECOND;
 const ONE_HOUR = 60 * ONE_MINUTE;
 
index fd751991b387062d4ad6026eb1530d3c243fdd5c..31f55f120e115b7de234e316ce72344a4d90dde0 100644 (file)
@@ -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);
index f4668d1ed78b29319486c8d384abe004919ef064..433f6bda39b52fa0f55cf9029eba4671b14c7b7c 100644 (file)
@@ -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) => {
index 7258e03991b6fd5dea058d5508f2b534d3aed6f1..c3be7edfde1f67200153ad4f8b2e8bf511f64daa 100644 (file)
@@ -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()));
index 710bca3d4ffd37efd76aca9c59c3d291e150be8a..9d8b36aed717b897a07e3a6c70e70114b49cb59e 100644 (file)
@@ -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<Props, State> {
   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 (
index f231e61eb1ca665d2c4488ae754a6b57c6da1496..65e6d4fee4dc82c5673697c48730036e98ebfbc8 100644 (file)
@@ -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<Props, State> {
       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<Props, State> {
       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 (
index 8ff95a870416181ec1c88f22dbb6da4401035a18..874ed9da3c197a0fa89d634fb7435bed10b93787 100644 (file)
@@ -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<Props, State> {
         resolved: 'false',
         rules: key,
       },
-      'projects',
+      FacetName.Projects,
     ).then(
       ({ facet, response }) => {
         if (this.mounted) {
@@ -138,7 +140,7 @@ export class RuleDetailsIssues extends React.PureComponent<Props, State> {
       <tr key={project.key}>
         <td className="coding-rules-detail-list-name">{project.name}</td>
         <td className="coding-rules-detail-list-parameters">
-          <Link to={path}>{formatMeasure(project.count, 'INT')}</Link>
+          <Link to={path}>{formatMeasure(project.count, MetricType.Integer)}</Link>
         </td>
       </tr>
     );
index f11ac3f8d4ef01b48bb2098ec65ec6b881b95367..e72f318e7df33e6c6f99d14d8d90e2307bace069 100644 (file)
@@ -43,7 +43,7 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props> {
   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<Props> {
   };
 
   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<Props> {
   };
 
   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 (
       <div className="coding-rules-detail-quality-profile-parameter" key={param.key}>
@@ -108,7 +107,7 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props> {
   );
 
   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<Props> {
   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 (
index dd518f5c59015c0c400f92086ab3adc3629bb75b..7af8515d6b03c9ee7379e1cef9d83d06a30e36e4 100644 (file)
@@ -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 }),
index 1778b6336fdb998bd3f5997c9a3a4dd3ba1b8a30..91703ee82b26626505b7afb4fadfbca7105d16c5 100644 (file)
@@ -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<Sidebar['props']> = {}) {
   return renderComponent(
     <Sidebar
index 08fe4aee127cf94e470c1ac19eb281ef8bc9ceb3..660e2a71256aacfba638ab2a3b90bdca4f6120f3 100644 (file)
@@ -124,7 +124,7 @@ function ProjectActivityAnalysis(props: ProjectActivityAnalysisProps) {
           )}
           aria-label={translateWithParameters(
             'project_activity.show_analysis_X_on_graph',
-            analysis.buildString || formatDate(parsedDate, formatterOption),
+            analysis.buildString ?? formatDate(parsedDate, formatterOption),
           )}
           onClick={() => props.onUpdateSelectedDate(analysis.date)}
           ref={(ref) => (node = ref)}
@@ -145,7 +145,7 @@ function ProjectActivityAnalysis(props: ProjectActivityAnalysisProps) {
                 <ActionsDropdown
                   ariaLabel={translateWithParameters(
                     'project_activity.analysis_X_actions',
-                    analysis.buildString || formatDate(parsedDate, formatterOption),
+                    analysis.buildString ?? formatDate(parsedDate, formatterOption),
                   )}
                   buttonSize="small"
                   id="it__analysis-actions"
index 0dd21ff8c92a4f2ef4d951ab0b82cee201734258..da33c27083a25963026dd54a0fb56137f9dac37d 100644 (file)
@@ -76,8 +76,7 @@ function referenceBranchDoesNotExist(
   existingBranches: Array<string>,
 ) {
   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) {
       <td className="huge-spacer-right nowrap">
         <Tooltip overlay={settingWarning}>
           <span>
-            {settingWarning && <WarningIcon className="little-spacer-right" />}
+            {settingWarning !== undefined && <WarningIcon className="little-spacer-right" />}
             {branch.newCodePeriod
               ? renderNewCodePeriodSetting(branch.newCodePeriod)
               : translate('branch_list.default_setting')}
index ee7b7b00ff9ede64fa5246c94cf860ef04af8b5c..b527f81c962eecd3d0d46262c32be2107cbd2834 100644 (file)
@@ -66,7 +66,7 @@ export class ProjectQualityProfilesApp extends React.PureComponent<Props, State>
 
   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<Props, State>
           // 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<Props, State>
     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,
index 316e48c441f8c12a6193311c9a94ae26daa3b292..6e83258278ed8f451a1dda5a9efd8f460e8d9e5f 100644 (file)
@@ -35,26 +35,31 @@ export type LanguageProfileSelectOptionProps = OptionProps<ProfileOption, false>
 export default function LanguageProfileSelectOption(props: LanguageProfileSelectOptionProps) {
   const option = props.data;
 
+  const SelectOptionDisableTooltipOverlay = React.useCallback(
+    () => (
+      <>
+        <p>
+          {translate(
+            'project_quality_profile.add_language_modal.profile_unavailable_no_active_rules',
+          )}
+        </p>
+        {option.label && option.language && (
+          <Link to={getQualityProfileUrl(option.label, option.language)}>
+            {translate('project_quality_profile.add_language_modal.go_to_profile')}
+          </Link>
+        )}
+      </>
+    ),
+    [option.label, option.language],
+  );
+
   return (
     <components.Option {...props}>
       <div>
         <DisableableSelectOption
           option={option}
           disabledReason={translate('project_quality_profile.add_language_modal.no_active_rules')}
-          disableTooltipOverlay={() => (
-            <>
-              <p>
-                {translate(
-                  'project_quality_profile.add_language_modal.profile_unavailable_no_active_rules',
-                )}
-              </p>
-              {option.label && option.language && (
-                <Link to={getQualityProfileUrl(option.label, option.language)}>
-                  {translate('project_quality_profile.add_language_modal.go_to_profile')}
-                </Link>
-              )}
-            </>
-          )}
+          disableTooltipOverlay={SelectOptionDisableTooltipOverlay}
         />
       </div>
     </components.Option>
index 0004e2e6edbc169d6238fad2d95a114e539695ff..a15e056eefd1278fbc30f3bde29aa4cff2280f71 100644 (file)
@@ -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,
     });
index 4cd3b4936b60a5b959df0b5240b6c9ae32245c85..663171479713d38f524bef467cf905b01a125b81 100644 (file)
@@ -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]));
   }
 }
index d00dd4a480a565dd81bb44eac8aaa2f3f05d12f6..efff71dbfa039f7f18736a2b6a71447312f13350 100644 (file)
@@ -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');
index d584a8d94c0a5bba973680301dcda0ba47c67e38..ed21ba444d94c51a0a3588dbd106db7ebbc6257e 100644 (file)
@@ -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 (
index 73718281fe47724af7c508a3c3bf51a2ffb98d67..1f330c1676734dab543b8edb502fb81092eeee41 100644 (file)
@@ -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 (
       <span className={className}>
@@ -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,
+              ),
             )}
         )
       </span>
index f9437cb79e9bd884182714cf88a894ba1be05baa..abe159f89592781a384de7c5ffb955fc3600655b 100644 (file)
@@ -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<RuleActivation[]>): 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 (
index 10be89523b46ee7aae90f17c0fdc752a67bf83d7..692aa128a90764848251472ac02e247eaeddc9ba 100644 (file)
@@ -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,
   );
 }
 
index 81751fd980b7be7193d7a5b73a918ef2851670c5..424281a6dd189d9d0c843a077b7b292a25d63d07 100644 (file)
@@ -88,7 +88,7 @@ export class WebApiApp extends React.PureComponent<Props, State> {
     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<Props, State> {
       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<Props, State> {
     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 }),
index 2e820ece5d56d6c0ddceb8dc0afbcd8173be5c47..7ce331f09b680ab691eb455276fb64b52a8a91b5 100644 (file)
@@ -38,7 +38,7 @@ export default function DeliveryItem({ className, delivery, loading, payload }:
       <p className="spacer-bottom">
         {translateWithParameters(
           'webhooks.delivery.response_x',
-          delivery.httpStatus || translate('webhooks.delivery.server_unreachable'),
+          delivery.httpStatus ?? translate('webhooks.delivery.server_unreachable'),
         )}
       </p>
       <p className="spacer-bottom">
@@ -49,7 +49,7 @@ export default function DeliveryItem({ className, delivery, loading, payload }:
       </p>
       <p className="spacer-bottom">{translate('webhooks.delivery.payload')}</p>
       <Spinner className="spacer-left spacer-top" loading={loading}>
-        {payload && <CodeSnippet noCopy snippet={formatPayload(payload)} />}
+        {payload !== undefined && <CodeSnippet noCopy snippet={formatPayload(payload)} />}
       </Spinner>
     </div>
   );
index 5264464bbb5261792eb6b195b3f02b00b04bc522..d3f489c5010d00fed651b678eb26159ad7b5eb5c 100644 (file)
@@ -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<Props, State> {
         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<IssueType>[]),
       };
     });
@@ -386,7 +387,10 @@ export default class MeasuresOverlay extends React.PureComponent<Props, State> {
         <div className="modal-body modal-container">
           <div className="measures-viewer-header big-spacer-bottom">
             <div>
-              <QualifierIcon className="little-spacer-right" qualifier="TRK" />
+              <QualifierIcon
+                className="little-spacer-right"
+                qualifier={ComponentQualifier.Project}
+              />
               <Link to={getBranchLikeUrl(sourceViewerFile.project, branchLike)}>
                 {sourceViewerFile.projectName}
               </Link>
@@ -402,7 +406,7 @@ export default class MeasuresOverlay extends React.PureComponent<Props, State> {
             <i className="spinner" />
           ) : (
             <>
-              {sourceViewerFile.q === 'UTS' ? (
+              {sourceViewerFile.q === ComponentQualifier.TestFile ? (
                 this.renderTests()
               ) : (
                 <div className="measures-viewer">
index 6a366358dc411479d3ccf95944566d2fdd099c0d..68bf691e3737504859ec5e3e5ec0d3be10ed45b4 100644 (file)
@@ -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,
index d62658300685b3f4fe998b81262572277747e308..d06e3d69aca0dbbf3bd8a27bce6d2364b074ca0e 100644 (file)
@@ -82,7 +82,7 @@ export class ComponentReportActions extends React.PureComponent<Props, State> {
       : '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();
 
index 40c7be0741db484edb3b5580ddd6b3d2fe01245c..2906d74a4374abc5815dac6d06ea39238daa49bb 100644 (file)
@@ -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))}
       </span>
       {button}
       {/* eslint-disable local-rules/no-conditional-rendering-of-deferredspinner */}
index 0a519dd212c06064d7fbd56a6d6ad61a307cb2af..aa2dc061402554e58fc1ae5d45f4106cc9e2653f 100644 (file)
@@ -93,28 +93,25 @@ export function multiValueRemove<
   return <components.MultiValueRemove {...props}>&times;</components.MultiValueRemove>;
 }
 
-/* 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<Option> = GroupBase<Option>,
-> extends React.Component<NamedProps<Option, IsMulti, Group> & StyleExtensionProps> {
-  render() {
-    return (
-      <ReactSelect<Option, IsMulti, Group>
-        {...omit(this.props, 'className', 'large')}
-        styles={selectStyle<Option, IsMulti, Group>(this.props)}
-        className={classNames('react-select', this.props.className)}
-        classNamePrefix="react-select"
-        components={{
-          ...this.props.components,
-          DropdownIndicator: dropdownIndicator,
-          ClearIndicator: clearIndicator,
-          MultiValueRemove: multiValueRemove,
-        }}
-      />
-    );
-  }
+>(props: NamedProps<Option, IsMulti, Group> & StyleExtensionProps) {
+  return (
+    <ReactSelect<Option, IsMulti, Group>
+      {...omit(props, 'className', 'large')}
+      styles={selectStyle<Option, IsMulti, Group>(props)}
+      className={classNames('react-select', props.className)}
+      classNamePrefix="react-select"
+      components={{
+        ...props.components,
+        DropdownIndicator: dropdownIndicator,
+        ClearIndicator: clearIndicator,
+        MultiValueRemove: multiValueRemove,
+      }}
+    />
+  );
 }
 
 export function CreatableSelect<
index d688d56bbed66f25282a7fd427d29cc1656a4e56..a317a752a495d4a22dc23619ffcc395dd541dfc6 100644 (file)
@@ -22,6 +22,7 @@ import { throttle, uniqueId } from 'lodash';
 import * as React from 'react';
 import { createPortal, findDOMNode } from 'react-dom';
 import { rawSizes } from '../../app/theme';
+import { ONE_SECOND } from '../../helpers/constants';
 import { translate } from '../../helpers/l10n';
 import EscKeydownHandler from './EscKeydownHandler';
 import FocusOutHandler from './FocusOutHandler';
@@ -101,7 +102,7 @@ export class TooltipInner extends React.Component<TooltipProps, State> {
     this.state = {
       flipped: false,
       placement: props.placement,
-      visible: props.visible !== undefined ? props.visible : false,
+      visible: props.visible ?? false,
     };
     this.id = uniqueId('tooltip-');
     this.throttledPositionTooltip = throttle(this.positionTooltip, 10);
@@ -163,11 +164,11 @@ export class TooltipInner extends React.Component<TooltipProps, State> {
   };
 
   isVisible = () => {
-    return this.props.visible !== undefined ? this.props.visible : this.state.visible;
+    return this.props.visible ?? this.state.visible;
   };
 
   getPlacement = (): Placement => {
-    return this.state.placement || 'bottom';
+    return this.state.placement ?? 'bottom';
   };
 
   tooltipNodeRef = (node: HTMLElement | null) => {
@@ -259,7 +260,7 @@ export class TooltipInner extends React.Component<TooltipProps, State> {
           this.setState({ visible: true });
         }
       },
-      (this.props.mouseEnterDelay || 0) * 1000,
+      (this.props.mouseEnterDelay ?? 0) * ONE_SECOND,
     );
 
     if (this.props.onShow) {
@@ -283,7 +284,7 @@ export class TooltipInner extends React.Component<TooltipProps, State> {
             this.props.onHide();
           }
         },
-        (this.props.mouseLeaveDelay || 0) * 1000,
+        (this.props.mouseLeaveDelay ?? 0) * ONE_SECOND,
       );
     }
   };
index 0d0faead164bf331c9b6a3530febdfc4438bbbee..4b5c2ea1d389dcfdda41f24e82ec26852987b8a6 100644 (file)
@@ -20,6 +20,7 @@
 import * as React from 'react';
 import { translate, translateWithParameters } from '../../helpers/l10n';
 import { formatMeasure } from '../../helpers/measures';
+import { MetricType } from '../../types/metrics';
 import { ButtonLink } from '../controls/buttons';
 
 interface Props {
@@ -49,7 +50,7 @@ export default class ListStyleFacetFooter extends React.PureComponent<Props> {
 
     return (
       <div className="note spacer-top spacer-bottom text-center">
-        {translateWithParameters('x_show', formatMeasure(count, 'INT', null))}
+        {translateWithParameters('x_show', formatMeasure(count, MetricType.Integer))}
 
         {hasMore && (
           <ButtonLink
diff --git a/server/sonar-web/src/main/js/components/issue/popups/CommentList.tsx b/server/sonar-web/src/main/js/components/issue/popups/CommentList.tsx
deleted file mode 100644 (file)
index 8ae05b4..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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 * as React from 'react';
-import { translate } from '../../../helpers/l10n';
-import { IssueComment } from '../../../types/types';
-import CommentTile from './CommentTile';
-
-interface CommentListProps {
-  comments?: IssueComment[];
-  deleteComment: (comment: string) => void;
-  onEdit: (comment: string, text: string) => void;
-}
-
-export default function CommentList(props: CommentListProps) {
-  const { comments } = props;
-  if (!comments || comments.length === 0) {
-    return (
-      <div className="note spacer-top spacer-bottom">{translate('issue.comment.empty.list')}</div>
-    );
-  }
-
-  // sorting comment i.e showing newest on top
-  const sortedComments = [...comments]?.sort(
-    (com1, com2) =>
-      new Date(com2.createdAt || '').getTime() - new Date(com1.createdAt || '').getTime(),
-  );
-  return (
-    <div className="issue-comment-list-wrapper spacer-bottom">
-      {sortedComments?.map((c) => (
-        <CommentTile
-          comment={c}
-          key={c.key}
-          handleDelete={props.deleteComment}
-          onEdit={props.onEdit}
-        />
-      ))}
-    </div>
-  );
-}
index c9eb0c6c779d0a9d1b7f8b68d9d56b72dab489f8..e22d91a2b67019c36a28b6d74b4bd44609ea6418 100644 (file)
@@ -28,7 +28,7 @@ import {
   getMaintainabilityGrid,
 } from '../../helpers/ratings';
 import { AppState } from '../../types/appstate';
-import { MetricKey } from '../../types/metrics';
+import { MetricKey, MetricType } from '../../types/metrics';
 import { GlobalSettingKeys } from '../../types/settings';
 import { KNOWN_RATINGS } from './utils';
 
@@ -54,7 +54,7 @@ export function RatingTooltipContent(props: RatingTooltipContentProps) {
   }
 
   const rating = Number(value);
-  const ratingLetter = formatMeasure(value, 'RATING');
+  const ratingLetter = formatMeasure(value, MetricType.Rating);
 
   if (finalMetricKey !== 'sqale_rating' && finalMetricKey !== 'maintainability_rating') {
     return <>{translate('metric', finalMetricKey, 'tooltip', ratingLetter)}</>;
@@ -71,12 +71,12 @@ export function RatingTooltipContent(props: RatingTooltipContentProps) {
       {rating === 1
         ? translateWithParameters(
             'metric.sqale_rating.tooltip.A',
-            formatMeasure(maintainabilityGrid[0] * PERCENT_MULTIPLIER, 'PERCENT'),
+            formatMeasure(maintainabilityGrid[0] * PERCENT_MULTIPLIER, MetricType.Percent),
           )
         : translateWithParameters(
             'metric.sqale_rating.tooltip',
             ratingLetter,
-            formatMeasure(maintainabilityRatingThreshold * PERCENT_MULTIPLIER, 'PERCENT'),
+            formatMeasure(maintainabilityRatingThreshold * PERCENT_MULTIPLIER, MetricType.Percent),
           )}
     </>
   );
index a3d30b183efa97e4780fb17a943eb5a0ac35a69c..c47d4ed9c21459e457f141ce32f9ff67d3c52399 100644 (file)
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
+import { MetricKey, MetricType } from '../../../types/metrics';
 import Measure from '../Measure';
 
 it('renders trivial measure', () => {
   expect(
-    shallow(<Measure metricKey="coverage" metricType="PERCENT" value="73.0" />),
+    shallow(
+      <Measure metricKey={MetricKey.coverage} metricType={MetricType.Percent} value="73.0" />,
+    ),
   ).toMatchSnapshot();
 });
 
 it('renders leak measure', () => {
   expect(
-    shallow(<Measure metricKey="new_coverage" metricType="PERCENT" value="36.0" />),
+    shallow(
+      <Measure metricKey={MetricKey.new_coverage} metricType={MetricType.Percent} value="36.0" />,
+    ),
   ).toMatchSnapshot();
 });
 
 it('renders LEVEL', () => {
   expect(
-    shallow(<Measure metricKey="quality_gate_status" metricType="LEVEL" value="ERROR" />),
+    shallow(
+      <Measure metricKey="quality_gate_status" metricType={MetricType.Level} value="ERROR" />,
+    ),
   ).toMatchSnapshot();
 });
 
 it('renders RATING', () => {
   expect(
-    shallow(<Measure metricKey="sqale_rating" metricType="RATING" value="3" />),
+    shallow(
+      <Measure metricKey={MetricKey.sqale_rating} metricType={MetricType.Rating} value="3" />,
+    ),
   ).toMatchSnapshot();
 });
 
 it('renders undefined measure', () => {
   expect(
-    shallow(<Measure metricKey="foo" metricType="PERCENT" value={undefined} />),
+    shallow(<Measure metricKey="foo" metricType={MetricType.Percent} value={undefined} />),
   ).toMatchSnapshot();
 });
index 7aa73124320fcb5b6fbdc1ceda782d64f2c2c22d..c6c715d16a1c9385a3b5118b11c470a2428c9ba4 100644 (file)
@@ -25,11 +25,11 @@ import { AppState } from '../../types/appstate';
 import { EditionKey } from '../../types/editions';
 import { SystemUpgrade } from '../../types/system';
 import Link from '../common/Link';
-import { ResetButtonLink } from '../controls/buttons';
 import Modal from '../controls/Modal';
+import { ResetButtonLink } from '../controls/buttons';
 import { Alert, AlertVariant } from '../ui/Alert';
 import SystemUpgradeItem from './SystemUpgradeItem';
-import { UpdateUseCase } from './utils';
+import { SYSTEM_VERSION_REGEXP, UpdateUseCase } from './utils';
 
 interface Props {
   appState: AppState;
@@ -45,82 +45,76 @@ const MAP_ALERT: { [key in UpdateUseCase]?: AlertVariant } = {
   [UpdateUseCase.PreviousLTS]: 'error',
 };
 
-interface State {
-  upgrading: boolean;
-}
+export function SystemUpgradeForm(props: Props) {
+  const { appState, latestLTS, onClose, updateUseCase, systemUpgrades } = props;
+
+  let systemUpgradesWithPatch: SystemUpgrade[][] = [];
+  const alertVariant = updateUseCase ? MAP_ALERT[updateUseCase] : undefined;
+  const header = translate('system.system_upgrade');
+  const parsedVersion = SYSTEM_VERSION_REGEXP.exec(appState.version);
+  let patches: SystemUpgrade[] = [];
+
+  if (updateUseCase === UpdateUseCase.NewPatch && parsedVersion !== null) {
+    const [, major, minor] = parsedVersion;
+    const majoMinorVersion = `${major}.${minor}`;
 
-export class SystemUpgradeForm extends React.PureComponent<Props, State> {
-  versionParser = /^(\d+)\.(\d+)(\.(\d+))?/;
-  state: State = { upgrading: false };
+    patches = flatMap(systemUpgrades, (upgrades) =>
+      filter(upgrades, (upgrade) => upgrade.version.startsWith(majoMinorVersion)),
+    );
+    systemUpgradesWithPatch = systemUpgrades
+      .map((upgrades) =>
+        upgrades.filter((upgrade) => !upgrade.version.startsWith(majoMinorVersion)),
+      )
+      .filter(negate(isEmpty));
+
+    systemUpgradesWithPatch.push(patches);
+  } else {
+    let untilLTS = false;
 
-  render() {
-    const { upgrading } = this.state;
-    const { appState, systemUpgrades, latestLTS, updateUseCase } = this.props;
-    let systemUpgradesWithPatch: SystemUpgrade[][] = [];
-    const alertVariant = updateUseCase ? MAP_ALERT[updateUseCase] : undefined;
-    const header = translate('system.system_upgrade');
-    const parsedVersion = this.versionParser.exec(appState.version);
-    let patches: SystemUpgrade[] = [];
-    if (updateUseCase === UpdateUseCase.NewPatch && parsedVersion !== null) {
-      const [, major, minor] = parsedVersion;
-      const majoMinorVersion = `${major}.${minor}`;
-      patches = flatMap(systemUpgrades, (upgrades) =>
-        filter(upgrades, (upgrade) => upgrade.version.startsWith(majoMinorVersion)),
-      );
-      systemUpgradesWithPatch = systemUpgrades
-        .map((upgrades) =>
-          upgrades.filter((upgrade) => !upgrade.version.startsWith(majoMinorVersion)),
-        )
-        .filter(negate(isEmpty));
-      systemUpgradesWithPatch.push(patches);
-    } else {
-      let untilLTS = false;
-      for (const upgrades of systemUpgrades) {
-        if (untilLTS === false) {
-          systemUpgradesWithPatch.push(upgrades);
-          untilLTS = upgrades.some((upgrade) => upgrade.version.startsWith(latestLTS));
-        }
+    for (const upgrades of systemUpgrades) {
+      if (untilLTS === false) {
+        systemUpgradesWithPatch.push(upgrades);
+        untilLTS = upgrades.some((upgrade) => upgrade.version.startsWith(latestLTS));
       }
     }
+  }
 
-    return (
-      <Modal contentLabel={header} onRequestClose={this.props.onClose}>
-        <div className="modal-head">
-          <h2>{header}</h2>
-        </div>
+  return (
+    <Modal contentLabel={header} onRequestClose={onClose}>
+      <div className="modal-head">
+        <h2>{header}</h2>
+      </div>
 
-        <div className="modal-body">
-          {alertVariant && updateUseCase && (
-            <Alert variant={alertVariant} className={`it__upgrade-alert-${updateUseCase}`}>
-              {translate('admin_notification.update', updateUseCase)}
-            </Alert>
-          )}
-          {systemUpgradesWithPatch.map((upgrades) => (
-            <SystemUpgradeItem
-              edition={
-                appState.edition as EditionKey /* TODO: Fix once AppState is no longer ambiant. */
-              }
-              key={upgrades[upgrades.length - 1].version}
-              systemUpgrades={upgrades}
-              isPatch={upgrades === patches}
-              isLTSVersion={upgrades.some((upgrade) => upgrade.version.startsWith(latestLTS))}
-            />
-          ))}
-        </div>
-        <div className="modal-foot">
-          {upgrading && <i className="spinner spacer-right" />}
-          <Link
-            className="pull-left link-no-underline display-flex-center"
-            to="https://www.sonarqube.org/downloads/?referrer=sonarqube"
-            target="_blank"
-          >
-            {translate('system.see_sonarqube_downloads')}
-          </Link>
-          <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink>
-        </div>
-      </Modal>
-    );
-  }
+      <div className="modal-body">
+        {alertVariant && updateUseCase && (
+          <Alert variant={alertVariant} className={`it__upgrade-alert-${updateUseCase}`}>
+            {translate('admin_notification.update', updateUseCase)}
+          </Alert>
+        )}
+        {systemUpgradesWithPatch.map((upgrades) => (
+          <SystemUpgradeItem
+            edition={
+              appState.edition as EditionKey /* TODO: Fix once AppState is no longer ambiant. */
+            }
+            key={upgrades[upgrades.length - 1].version}
+            systemUpgrades={upgrades}
+            isPatch={upgrades === patches}
+            isLTSVersion={upgrades.some((upgrade) => upgrade.version.startsWith(latestLTS))}
+          />
+        ))}
+      </div>
+      <div className="modal-foot">
+        <Link
+          className="pull-left link-no-underline display-flex-center"
+          to="https://www.sonarqube.org/downloads/?referrer=sonarqube"
+          target="_blank"
+        >
+          {translate('system.see_sonarqube_downloads')}
+        </Link>
+        <ResetButtonLink onClick={onClose}>{translate('cancel')}</ResetButtonLink>
+      </div>
+    </Modal>
+  );
 }
 
 export default withAppStateContext(SystemUpgradeForm);
index 607523f97feebea47aa20885314206ae4260e4dc..5af6f68a40cf143f2d2943de80df31c1f66b2608 100644 (file)
@@ -27,6 +27,8 @@ export enum UpdateUseCase {
   PreviousLTS = 'previous_lts',
 }
 
+export const SYSTEM_VERSION_REGEXP = /^(\d+)\.(\d+)(\.(\d+))?/;
+
 export function sortUpgrades(upgrades: SystemUpgrade[]): SystemUpgrade[] {
   return sortBy(upgrades, [
     (upgrade: SystemUpgrade) => -Number(upgrade.version.split('.')[0]),
index 8781c8d195c4bdf0d28d535eeee5a2384e3dfa64..6fd146ecf545ebdee1cbe3f440af32ca82c4d1e3 100644 (file)
@@ -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 { MetricKey, MetricType } from '../../types/metrics';
 import { Dict } from '../../types/types';
 import { getMessages } from '../l10nBundle';
 import { enhanceConditionWithMeasure, formatMeasure, isPeriodBestValue } from '../measures';
@@ -56,21 +57,29 @@ const ONE_DAY = HOURS_IN_DAY * ONE_HOUR;
 describe('enhanceConditionWithMeasure', () => {
   it('should correctly map enhance conditions with measure data', () => {
     const measures = [
-      mockMeasureEnhanced({ metric: mockMetric({ key: 'bugs' }), period: undefined }),
-      mockMeasureEnhanced({ metric: mockMetric({ key: 'new_bugs' }) }),
+      mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.bugs }), period: undefined }),
+      mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_bugs }) }),
     ];
 
     expect(
-      enhanceConditionWithMeasure(mockQualityGateStatusCondition({ metric: 'bugs' }), measures),
+      enhanceConditionWithMeasure(
+        mockQualityGateStatusCondition({ metric: MetricKey.bugs }),
+        measures,
+      ),
     ).toMatchObject({
-      measure: expect.objectContaining({ metric: expect.objectContaining({ key: 'bugs' }) }),
+      measure: expect.objectContaining({
+        metric: expect.objectContaining({ key: MetricKey.bugs }),
+      }),
     });
 
     expect(
-      enhanceConditionWithMeasure(mockQualityGateStatusCondition({ metric: 'new_bugs' }), measures),
+      enhanceConditionWithMeasure(
+        mockQualityGateStatusCondition({ metric: MetricKey.new_bugs }),
+        measures,
+      ),
     ).toMatchObject({
       measure: expect.objectContaining({
-        metric: expect.objectContaining({ key: 'new_bugs' }),
+        metric: expect.objectContaining({ key: MetricKey.new_bugs }),
       }),
       period: 1,
     });
@@ -99,30 +108,30 @@ describe('isPeriodBestValue', () => {
 
 describe('#formatMeasure()', () => {
   it('should format INT', () => {
-    expect(formatMeasure(0, 'INT')).toBe('0');
-    expect(formatMeasure(1, 'INT')).toBe('1');
-    expect(formatMeasure(-5, 'INT')).toBe('-5');
-    expect(formatMeasure(999, 'INT')).toBe('999');
-    expect(formatMeasure(1000, 'INT')).toBe('1,000');
-    expect(formatMeasure(1529, 'INT')).toBe('1,529');
-    expect(formatMeasure(10000, 'INT')).toBe('10,000');
-    expect(formatMeasure(1234567890, 'INT')).toBe('1,234,567,890');
+    expect(formatMeasure(0, MetricType.Integer)).toBe('0');
+    expect(formatMeasure(1, MetricType.Integer)).toBe('1');
+    expect(formatMeasure(-5, MetricType.Integer)).toBe('-5');
+    expect(formatMeasure(999, MetricType.Integer)).toBe('999');
+    expect(formatMeasure(1000, MetricType.Integer)).toBe('1,000');
+    expect(formatMeasure(1529, MetricType.Integer)).toBe('1,529');
+    expect(formatMeasure(10000, MetricType.Integer)).toBe('10,000');
+    expect(formatMeasure(1234567890, MetricType.Integer)).toBe('1,234,567,890');
   });
 
   it('should format SHORT_INT', () => {
-    expect(formatMeasure(0, 'SHORT_INT')).toBe('0');
-    expect(formatMeasure(1, 'SHORT_INT')).toBe('1');
-    expect(formatMeasure(999, 'SHORT_INT')).toBe('999');
-    expect(formatMeasure(1000, 'SHORT_INT')).toBe('1k');
-    expect(formatMeasure(1529, 'SHORT_INT')).toBe('1.5k');
-    expect(formatMeasure(10000, 'SHORT_INT')).toBe('10k');
-    expect(formatMeasure(10678, 'SHORT_INT')).toBe('11k');
-    expect(formatMeasure(9467890, 'SHORT_INT')).toBe('9.5M');
-    expect(formatMeasure(994567890, 'SHORT_INT')).toBe('995M');
-    expect(formatMeasure(999000001, 'SHORT_INT')).toBe('999M');
-    expect(formatMeasure(999567890, 'SHORT_INT')).toBe('1G');
-    expect(formatMeasure(1234567890, 'SHORT_INT')).toBe('1.2G');
-    expect(formatMeasure(11234567890, 'SHORT_INT')).toBe('11G');
+    expect(formatMeasure(0, MetricType.ShortInteger)).toBe('0');
+    expect(formatMeasure(1, MetricType.ShortInteger)).toBe('1');
+    expect(formatMeasure(999, MetricType.ShortInteger)).toBe('999');
+    expect(formatMeasure(1000, MetricType.ShortInteger)).toBe('1k');
+    expect(formatMeasure(1529, MetricType.ShortInteger)).toBe('1.5k');
+    expect(formatMeasure(10000, MetricType.ShortInteger)).toBe('10k');
+    expect(formatMeasure(10678, MetricType.ShortInteger)).toBe('11k');
+    expect(formatMeasure(9467890, MetricType.ShortInteger)).toBe('9.5M');
+    expect(formatMeasure(994567890, MetricType.ShortInteger)).toBe('995M');
+    expect(formatMeasure(999000001, MetricType.ShortInteger)).toBe('999M');
+    expect(formatMeasure(999567890, MetricType.ShortInteger)).toBe('1G');
+    expect(formatMeasure(1234567890, MetricType.ShortInteger)).toBe('1.2G');
+    expect(formatMeasure(11234567890, MetricType.ShortInteger)).toBe('11G');
   });
 
   it('should format FLOAT', () => {
@@ -145,28 +154,28 @@ describe('#formatMeasure()', () => {
   });
 
   it('should format PERCENT', () => {
-    expect(formatMeasure(0.0, 'PERCENT')).toBe('0.0%');
-    expect(formatMeasure(1.0, 'PERCENT')).toBe('1.0%');
-    expect(formatMeasure(1.3, 'PERCENT')).toBe('1.3%');
-    expect(formatMeasure(1.34, 'PERCENT')).toBe('1.3%');
-    expect(formatMeasure(50.89, 'PERCENT')).toBe('50.9%');
-    expect(formatMeasure(100.0, 'PERCENT')).toBe('100%');
-    expect(formatMeasure(50.89, 'PERCENT', { decimals: 0 })).toBe('50.9%');
-    expect(formatMeasure(50.89, 'PERCENT', { decimals: 1 })).toBe('50.9%');
-    expect(formatMeasure(50.89, 'PERCENT', { decimals: 2 })).toBe('50.89%');
-    expect(formatMeasure(50.89, 'PERCENT', { decimals: 3 })).toBe('50.890%');
-    expect(formatMeasure(50, 'PERCENT', { decimals: 0, omitExtraDecimalZeros: true })).toBe(
-      '50.0%',
-    );
-    expect(formatMeasure(50, 'PERCENT', { decimals: 1, omitExtraDecimalZeros: true })).toBe(
-      '50.0%',
-    );
-    expect(formatMeasure(50, 'PERCENT', { decimals: 3, omitExtraDecimalZeros: true })).toBe(
-      '50.0%',
-    );
-    expect(formatMeasure(50.89, 'PERCENT', { decimals: 3, omitExtraDecimalZeros: true })).toBe(
-      '50.89%',
-    );
+    expect(formatMeasure(0.0, MetricType.Percent)).toBe('0.0%');
+    expect(formatMeasure(1.0, MetricType.Percent)).toBe('1.0%');
+    expect(formatMeasure(1.3, MetricType.Percent)).toBe('1.3%');
+    expect(formatMeasure(1.34, MetricType.Percent)).toBe('1.3%');
+    expect(formatMeasure(50.89, MetricType.Percent)).toBe('50.9%');
+    expect(formatMeasure(100.0, MetricType.Percent)).toBe('100%');
+    expect(formatMeasure(50.89, MetricType.Percent, { decimals: 0 })).toBe('50.9%');
+    expect(formatMeasure(50.89, MetricType.Percent, { decimals: 1 })).toBe('50.9%');
+    expect(formatMeasure(50.89, MetricType.Percent, { decimals: 2 })).toBe('50.89%');
+    expect(formatMeasure(50.89, MetricType.Percent, { decimals: 3 })).toBe('50.890%');
+    expect(
+      formatMeasure(50, MetricType.Percent, { decimals: 0, omitExtraDecimalZeros: true }),
+    ).toBe('50.0%');
+    expect(
+      formatMeasure(50, MetricType.Percent, { decimals: 1, omitExtraDecimalZeros: true }),
+    ).toBe('50.0%');
+    expect(
+      formatMeasure(50, MetricType.Percent, { decimals: 3, omitExtraDecimalZeros: true }),
+    ).toBe('50.0%');
+    expect(
+      formatMeasure(50.89, MetricType.Percent, { decimals: 3, omitExtraDecimalZeros: true }),
+    ).toBe('50.89%');
   });
 
   it('should format WORK_DUR', () => {
@@ -185,42 +194,48 @@ describe('#formatMeasure()', () => {
   });
 
   it('should format SHORT_WORK_DUR', () => {
-    expect(formatMeasure(0, 'SHORT_WORK_DUR')).toBe('0');
-    expect(formatMeasure(5 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('5d');
-    expect(formatMeasure(2 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('2h');
-    expect(formatMeasure(ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('1min');
-    expect(formatMeasure(40 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('40min');
-    expect(formatMeasure(58 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('1h');
-    expect(formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('5d');
-    expect(formatMeasure(2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('2h');
-    expect(formatMeasure(ONE_HOUR + 55 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('2h');
-    expect(formatMeasure(3 * ONE_DAY + 6 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('4d');
-    expect(formatMeasure(7 * ONE_HOUR + 59 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('1d');
-    expect(formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('5d');
-    expect(formatMeasure(15 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('15d');
-    expect(formatMeasure(7 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('7min');
-    expect(formatMeasure(-5 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('-5d');
-    expect(formatMeasure(-2 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('-2h');
-    expect(formatMeasure(-1 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('-1min');
-
-    expect(formatMeasure(1529 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('1.5kd');
-    expect(formatMeasure(1234567 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('1.2Md');
-    expect(formatMeasure(12345670 * ONE_DAY + 4 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('12Md');
+    expect(formatMeasure(0, MetricType.ShortWorkDuration)).toBe('0');
+    expect(formatMeasure(5 * ONE_DAY, MetricType.ShortWorkDuration)).toBe('5d');
+    expect(formatMeasure(2 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe('2h');
+    expect(formatMeasure(ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('1min');
+    expect(formatMeasure(40 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('40min');
+    expect(formatMeasure(58 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('1h');
+    expect(formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe('5d');
+    expect(formatMeasure(2 * ONE_HOUR + ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('2h');
+    expect(formatMeasure(ONE_HOUR + 55 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('2h');
+    expect(formatMeasure(3 * ONE_DAY + 6 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe('4d');
+    expect(formatMeasure(7 * ONE_HOUR + 59 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('1d');
+    expect(
+      formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, MetricType.ShortWorkDuration),
+    ).toBe('5d');
+    expect(
+      formatMeasure(15 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, MetricType.ShortWorkDuration),
+    ).toBe('15d');
+    expect(formatMeasure(7 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('7min');
+    expect(formatMeasure(-5 * ONE_DAY, MetricType.ShortWorkDuration)).toBe('-5d');
+    expect(formatMeasure(-2 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe('-2h');
+    expect(formatMeasure(-1 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('-1min');
+
+    expect(formatMeasure(1529 * ONE_DAY, MetricType.ShortWorkDuration)).toBe('1.5kd');
+    expect(formatMeasure(1234567 * ONE_DAY, MetricType.ShortWorkDuration)).toBe('1.2Md');
+    expect(formatMeasure(12345670 * ONE_DAY + 4 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe(
+      '12Md',
+    );
   });
 
   it('should format RATING', () => {
-    expect(formatMeasure(1, 'RATING')).toBe('A');
-    expect(formatMeasure(2, 'RATING')).toBe('B');
-    expect(formatMeasure(3, 'RATING')).toBe('C');
-    expect(formatMeasure(4, 'RATING')).toBe('D');
-    expect(formatMeasure(5, 'RATING')).toBe('E');
+    expect(formatMeasure(1, MetricType.Rating)).toBe('A');
+    expect(formatMeasure(2, MetricType.Rating)).toBe('B');
+    expect(formatMeasure(3, MetricType.Rating)).toBe('C');
+    expect(formatMeasure(4, MetricType.Rating)).toBe('D');
+    expect(formatMeasure(5, MetricType.Rating)).toBe('E');
   });
 
   it('should format LEVEL', () => {
-    expect(formatMeasure('ERROR', 'LEVEL')).toBe('Error');
-    expect(formatMeasure('WARN', 'LEVEL')).toBe('Warning');
-    expect(formatMeasure('OK', 'LEVEL')).toBe('Ok');
-    expect(formatMeasure('UNKNOWN', 'LEVEL')).toBe('UNKNOWN');
+    expect(formatMeasure('ERROR', MetricType.Level)).toBe('Error');
+    expect(formatMeasure('WARN', MetricType.Level)).toBe('Warning');
+    expect(formatMeasure('OK', MetricType.Level)).toBe('Ok');
+    expect(formatMeasure('UNKNOWN', MetricType.Level)).toBe('UNKNOWN');
   });
 
   it('should format MILLISEC', () => {
@@ -237,10 +252,10 @@ describe('#formatMeasure()', () => {
   });
 
   it('should return null if value is empty string', () => {
-    expect(formatMeasure('', 'PERCENT')).toBe('');
+    expect(formatMeasure('', MetricType.Percent)).toBe('');
   });
 
   it('should not fail with undefined', () => {
-    expect(formatMeasure(undefined, 'INT')).toBe('');
+    expect(formatMeasure(undefined, MetricType.Integer)).toBe('');
   });
 });
index 393b037269e0720a83c918a41a1b8cbeae79d1b3..4f974fccf3cbfa88a72b68318d26d29243462583 100644 (file)
@@ -99,3 +99,5 @@ export const IMPORT_COMPATIBLE_ALMS = [
 ];
 
 export const GRADLE_SCANNER_VERSION = '4.3.1.3277';
+
+export const ONE_SECOND = 1000;
index df9c80dc754cf07d1abde08f42eb7da86d217397..bd4ba2385dfa060d10f48aaa7cd959db31623d45 100644 (file)
@@ -115,13 +115,13 @@ function splitFlows(
     };
   }
 
-  const parsedFlows: FlowLocation[][] = (issue.flows || [])
+  const parsedFlows: FlowLocation[][] = (issue.flows ?? [])
     .filter((flow) => flow.locations !== undefined)
     .map((flow) => flow.locations!.filter((location) => location.textRange != null))
     .map((flow) =>
       flow.map((location) => {
         const component = components.find((component) => component.key === location.component);
-        return { ...location, componentName: component && component.name };
+        return { ...location, componentName: component?.name };
       }),
     );
 
@@ -139,8 +139,8 @@ function splitFlows(
 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,
   );
 }
 
index 6eea63084122d823e9fec11f20ce649e1fe507bb..3eda07fb3343d9c3337cd10fb84cb314c9156d7c 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { MetricKey } from '../types/metrics';
+import { MetricKey, MetricType } from '../types/metrics';
 import {
   QualityGateStatusCondition,
   QualityGateStatusConditionEnhanced,
 } from '../types/quality-gates';
 import { Dict, Measure, MeasureEnhanced, Metric } from '../types/types';
+import { ONE_SECOND } from './constants';
 import { translate, translateWithParameters } from './l10n';
 import { getCurrentLocale } from './l10nBundle';
 import { isDefined } from './types';
@@ -48,7 +49,7 @@ export function enhanceConditionWithMeasure(
   // Make sure we have a period index. This is necessary when dealing with
   // applications.
   let { period } = condition;
-  if (measure && measure.period && !period) {
+  if (measure?.period && !period) {
     period = measure.period.index;
   }
 
@@ -56,12 +57,12 @@ export function enhanceConditionWithMeasure(
 }
 
 export function isPeriodBestValue(measure: Measure | MeasureEnhanced): boolean {
-  return measure.period?.bestValue || false;
+  return measure.period?.bestValue ?? false;
 }
 
 /** Check if metric is differential */
 export function isDiffMetric(metricKey: MetricKey | string): boolean {
-  return metricKey.indexOf('new_') === 0;
+  return metricKey.startsWith('new_');
 }
 
 export function getDisplayMetrics(metrics: Metric[]) {
@@ -74,9 +75,7 @@ export function findMeasure(measures: MeasureEnhanced[], metric: MetricKey | str
 
 const HOURS_IN_DAY = 8;
 
-interface Formatter {
-  (value: string | number, options?: any): string;
-}
+type Formatter = (value: string | number, options?: Dict<unknown>) => string;
 
 /**
  * Format a measure value for a given type
@@ -85,7 +84,7 @@ interface Formatter {
 export function formatMeasure(
   value: string | number | undefined,
   type: string,
-  options?: any,
+  options?: Dict<unknown>,
 ): string {
   const formatter = getFormatter(type);
   // eslint-disable-next-line react-hooks/rules-of-hooks
@@ -114,10 +113,10 @@ export function localizeMetric(metricKey: string): string {
 
 /** Return corresponding "short" for better display in UI */
 export function getShortType(type: string): string {
-  if (type === 'INT') {
-    return 'SHORT_INT';
+  if (type === MetricType.Integer) {
+    return MetricType.ShortInteger;
   } else if (type === 'WORK_DUR') {
-    return 'SHORT_WORK_DUR';
+    return MetricType.ShortWorkDuration;
   }
   return type;
 }
@@ -125,7 +124,7 @@ export function getShortType(type: string): string {
 function useFormatter(
   value: string | number | undefined,
   formatter: Formatter,
-  options?: any,
+  options?: Dict<unknown>,
 ): string {
   return value !== undefined && value !== '' ? formatter(value, options) : '';
 }
@@ -181,7 +180,7 @@ function shortIntFormatter(
   value: string | number,
   option?: { roundingFunc?: (x: number) => number },
 ): string {
-  const roundingFunc = (option && option.roundingFunc) || undefined;
+  const roundingFunc = option?.roundingFunc;
   if (typeof value === 'string') {
     value = parseFloat(value);
   }
@@ -259,7 +258,6 @@ function millisecondsFormatter(value: string | number): string {
   if (typeof value === 'string') {
     value = parseInt(value, 10);
   }
-  const ONE_SECOND = 1000;
   const ONE_MINUTE = 60 * ONE_SECOND;
   if (value >= ONE_MINUTE) {
     const minutes = Math.round(value / ONE_MINUTE);
@@ -329,7 +327,10 @@ function formatDurationShort(
 ): string {
   if (shouldDisplayDaysInShortFormat(days)) {
     const roundedDays = Math.round(days);
-    const formattedDays = formatMeasure(isNegative ? -1 * roundedDays : roundedDays, 'SHORT_INT');
+    const formattedDays = formatMeasure(
+      isNegative ? -1 * roundedDays : roundedDays,
+      MetricType.ShortInteger,
+    );
     return translateWithParameters('work_duration.x_days', formattedDays);
   }
 
@@ -337,12 +338,15 @@ function formatDurationShort(
     const roundedHours = Math.round(hours);
     const formattedHours = formatMeasure(
       isNegative ? -1 * roundedHours : roundedHours,
-      'SHORT_INT',
+      MetricType.ShortInteger,
     );
     return translateWithParameters('work_duration.x_hours', formattedHours);
   }
 
-  const formattedMinutes = formatMeasure(isNegative ? -1 * minutes : minutes, 'SHORT_INT');
+  const formattedMinutes = formatMeasure(
+    isNegative ? -1 * minutes : minutes,
+    MetricType.ShortInteger,
+  );
   return translateWithParameters('work_duration.x_minutes', formattedMinutes);
 }
 
index 0e63ce841a015654a733a41664e6fe937e694349..14b7872cbd2f3cd93162c362eed05e99d3039269 100644 (file)
@@ -32,7 +32,7 @@ export function orderLinks<T extends NameAndType>(links: T[]) {
   const [provided, unknown] = partition<T>(links, isProvided);
   return [
     ...sortBy(provided, (link) => PROVIDED_TYPES.indexOf(link.type)),
-    ...sortBy(unknown, (link) => link.name && link.name.toLowerCase()),
+    ...sortBy(unknown, (link) => link.name?.toLowerCase()),
   ];
 }
 
index 9f0e415787ffed061360bbd6964f1ce225f4c245..95005676fcfbdb704ad50aa5810d2300adda6eb6 100644 (file)
@@ -24,6 +24,9 @@ import { translate } from './l10n';
 import { stringify } from './stringify-queryparams';
 import { getBaseUrl } from './system';
 
+const FAST_RETRY_TIMEOUT = 500;
+const SLOW_RETRY_TIMEOUT = 3000;
+
 export function getCSRFTokenName(): string {
   return 'X-XSRF-TOKEN';
 }
@@ -117,12 +120,12 @@ class Request {
     return window.fetch(getBaseUrl() + url, options);
   }
 
-  setMethod(method: string): Request {
+  setMethod(method: string): this {
     this.options.method = method;
     return this;
   }
 
-  setData(data?: RequestData, isJSON = false): Request {
+  setData(data?: RequestData, isJSON = false): this {
     if (data) {
       this.data = data;
       this.isJSON = isJSON;
@@ -154,7 +157,7 @@ export function corsRequest(url: string, mode: RequestMode = 'cors'): Request {
 /**
  * Check that response status is ok
  */
-export function checkStatus(response: Response, bypassRedirect?: boolean): Promise<Response> {
+export function checkStatus(response: Response, bypassRedirect = false): Promise<Response> {
   return new Promise((resolve, reject) => {
     if (response.status === HttpStatus.Unauthorized && !bypassRedirect) {
       import('./handleRequiredAuthentication').then((i) => i.default()).then(reject, reject);
@@ -193,7 +196,7 @@ export function parseError(response: Response): Promise<string> {
 /**
  * Shortcut to do a GET request and return a Response
  */
-export function get(url: string, data?: RequestData, bypassRedirect?: boolean): Promise<Response> {
+export function get(url: string, data?: RequestData, bypassRedirect = false): Promise<Response> {
   return request(url)
     .setData(data)
     .submit()
@@ -203,18 +206,14 @@ export function get(url: string, data?: RequestData, bypassRedirect?: boolean):
 /**
  * Shortcut to do a GET request and return response json
  */
-export function getJSON(url: string, data?: RequestData, bypassRedirect?: boolean): Promise<any> {
+export function getJSON(url: string, data?: RequestData, bypassRedirect = false): Promise<any> {
   return get(url, data, bypassRedirect).then(parseJSON);
 }
 
 /**
  * Shortcut to do a GET request and return response text
  */
-export function getText(
-  url: string,
-  data?: RequestData,
-  bypassRedirect?: boolean,
-): Promise<string> {
+export function getText(url: string, data?: RequestData, bypassRedirect = false): Promise<string> {
   return get(url, data, bypassRedirect).then(parseText);
 }
 
@@ -236,7 +235,7 @@ export function getCorsJSON(url: string, data?: RequestData): Promise<any> {
 /**
  * Shortcut to do a POST request and return response json
  */
-export function postJSON(url: string, data?: RequestData, bypassRedirect?: boolean): Promise<any> {
+export function postJSON(url: string, data?: RequestData, bypassRedirect = false): Promise<any> {
   return request(url)
     .setMethod('POST')
     .setData(data)
@@ -251,7 +250,7 @@ export function postJSON(url: string, data?: RequestData, bypassRedirect?: boole
 export function postJSONBody(
   url: string,
   data?: RequestData,
-  bypassRedirect?: boolean,
+  bypassRedirect = false,
 ): Promise<any> {
   return request(url)
     .setMethod('POST')
@@ -264,7 +263,7 @@ export function postJSONBody(
 /**
  * Shortcut to do a POST request
  */
-export function post(url: string, data?: RequestData, bypassRedirect?: boolean): Promise<void> {
+export function post(url: string, data?: RequestData, bypassRedirect = false): Promise<void> {
   return new Promise((resolve, reject) => {
     request(url)
       .setMethod('POST')
@@ -298,7 +297,7 @@ function tryRequestAgain<T>(
     return new Promise<T>((resolve) => {
       setTimeout(
         () => resolve(requestTryAndRepeatUntil(repeatAPICall, tries, stopRepeat, repeatErrors)),
-        tries.max > tries.slowThreshold ? 500 : 3000,
+        tries.max > tries.slowThreshold ? FAST_RETRY_TIMEOUT : SLOW_RETRY_TIMEOUT,
       );
     });
   }
index 116bdcf3c89d0b8a94b8730c82f8c6549ed233e4..4c14ed7c0d74f77a9bfac6c35de9413738362779 100644 (file)
@@ -216,3 +216,24 @@ export interface RawFacet {
 export interface Facet {
   [value: string]: number;
 }
+
+export enum FacetName {
+  AssignedToMe = 'assigned_to_me',
+  Assignees = 'assignees',
+  Author = 'author',
+  CodeVariants = 'codeVariants',
+  CreatedAt = 'createdAt',
+  Cwe = 'cwe',
+  Directories = 'directories',
+  Files = 'files',
+  Languages = 'languages',
+  OwaspTop10 = 'owaspTop10',
+  Projects = 'projects',
+  Reporters = 'reporters',
+  Resolutions = 'resolutions',
+  Rules = 'rules',
+  Severities = 'severities',
+  Statuses = 'statuses',
+  Tags = 'tags',
+  Types = 'types',
+}