]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-15938 Improve code sharing with the license extension
authorPhilippe Perrin <philippe.perrin@sonarsource.com>
Fri, 28 Jan 2022 16:39:50 +0000 (17:39 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 29 Mar 2022 20:03:37 +0000 (20:03 +0000)
76 files changed:
server/sonar-web/config/jest/SetupTestEnvironment.ts
server/sonar-web/src/main/js/api/alm-integrations.ts
server/sonar-web/src/main/js/api/alm-settings.ts
server/sonar-web/src/main/js/api/application.ts
server/sonar-web/src/main/js/api/branches.ts
server/sonar-web/src/main/js/api/ce.ts
server/sonar-web/src/main/js/api/component-report.ts
server/sonar-web/src/main/js/api/components.ts
server/sonar-web/src/main/js/api/editions.ts [new file with mode: 0644]
server/sonar-web/src/main/js/api/issues.ts
server/sonar-web/src/main/js/api/l10n.ts
server/sonar-web/src/main/js/api/languages.ts
server/sonar-web/src/main/js/api/marketplace.ts [deleted file]
server/sonar-web/src/main/js/api/measures.ts
server/sonar-web/src/main/js/api/metrics.ts
server/sonar-web/src/main/js/api/nav.ts
server/sonar-web/src/main/js/api/newCodePeriod.ts
server/sonar-web/src/main/js/api/notifications.ts
server/sonar-web/src/main/js/api/permissions.ts
server/sonar-web/src/main/js/api/plugins.ts
server/sonar-web/src/main/js/api/project-badges.ts
server/sonar-web/src/main/js/api/project-dump.ts
server/sonar-web/src/main/js/api/projectActivity.ts
server/sonar-web/src/main/js/api/projectLinks.ts
server/sonar-web/src/main/js/api/quality-gates.ts
server/sonar-web/src/main/js/api/quality-profiles.ts
server/sonar-web/src/main/js/api/rules.ts
server/sonar-web/src/main/js/api/security-hotspots.ts
server/sonar-web/src/main/js/api/settings.ts
server/sonar-web/src/main/js/api/system.ts
server/sonar-web/src/main/js/api/time-machine.ts
server/sonar-web/src/main/js/api/user-tokens.ts
server/sonar-web/src/main/js/api/user_groups.ts
server/sonar-web/src/main/js/api/users.ts
server/sonar-web/src/main/js/api/web-api.ts
server/sonar-web/src/main/js/api/webhooks.ts
server/sonar-web/src/main/js/app/components/StartupModal.tsx
server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx
server/sonar-web/src/main/js/app/components/extensions/Extension.tsx
server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavLicenseNotif.tsx
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavLicenseNotif-test.tsx
server/sonar-web/src/main/js/app/index.ts
server/sonar-web/src/main/js/app/utils/__tests__/throwGlobalError-test.ts [deleted file]
server/sonar-web/src/main/js/app/utils/exportModulesAsGlobals.ts [new file with mode: 0644]
server/sonar-web/src/main/js/app/utils/startReactApp.tsx
server/sonar-web/src/main/js/app/utils/throwGlobalError.ts [deleted file]
server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/DomainFacet-test.tsx.snap
server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewerWrapper.tsx
server/sonar-web/src/main/js/apps/permissions/__tests__/utils-test.ts
server/sonar-web/src/main/js/apps/projectDump/ProjectDumpApp.tsx
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AllCategoriesList-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/DefinitionRenderer-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Languages-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SubCategoryDefinitionsList-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx
server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx
server/sonar-web/src/main/js/apps/users/components/UserForm.tsx
server/sonar-web/src/main/js/components/issue/__tests__/actions-test.ts
server/sonar-web/src/main/js/components/issue/actions.ts
server/sonar-web/src/main/js/helpers/__tests__/error-test.ts [new file with mode: 0644]
server/sonar-web/src/main/js/helpers/__tests__/l10n-test.ts
server/sonar-web/src/main/js/helpers/__tests__/l10nBundle-test.ts [new file with mode: 0644]
server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts
server/sonar-web/src/main/js/helpers/error.ts [new file with mode: 0644]
server/sonar-web/src/main/js/helpers/l10n.ts
server/sonar-web/src/main/js/helpers/l10nBundle.ts [new file with mode: 0644]
server/sonar-web/src/main/js/helpers/measures.ts
server/sonar-web/src/main/js/helpers/mocks/editions.ts [new file with mode: 0644]
server/sonar-web/src/main/js/types/browser.ts
server/sonar-web/src/main/js/types/editions.ts
server/sonar-web/src/main/js/types/extension.ts
server/sonar-web/src/main/js/types/l10n.ts [deleted file]
server/sonar-web/src/main/js/types/l10nBundle.ts [new file with mode: 0644]
server/sonar-web/src/main/js/types/settings.ts

index b590a82fc677ce600d66c193cccd00df3daeed0a..d4ce2e93c3cabe20a754f8a97f60cc7182c92e69 100644 (file)
@@ -23,3 +23,11 @@ document.documentElement.appendChild(content);
 
 const baseUrl = '';
 (window as any).baseUrl = baseUrl;
+
+jest.mock('../../src/main/js/helpers/l10n', () => ({
+  ...jest.requireActual('../../src/main/js/helpers/l10n'),
+  hasMessage: () => true,
+  translate: (...keys: string[]) => keys.join('.'),
+  translateWithParameters: (messageKey: string, ...parameters: Array<string | number>) =>
+    [messageKey, ...parameters].join('.')
+}));
index 32186b51d98abd47db4593a747e14c5bc4a99305..1cd48a9e80a27ef44f41f38605aac1a3e94a66db 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { get, getJSON, parseError, post, postJSON } from '../helpers/request';
 import {
   AzureProject,
index 218ca9de03b24d924478a9eac4a035c1511fc3fa..d4d4e3f4c385676cb283e998e62af1e01eb684e5 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { get, getJSON, HttpStatus, parseError, parseJSON, post } from '../helpers/request';
 import {
   AlmSettingsBindingDefinitions,
index 5e5ca222d55e86a792717fddcf756643bf08687d..99bd71ef4a93eb23d0b1ea1e471aabe002075854 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post, postJSON } from '../helpers/request';
 import { Application, ApplicationPeriod } from '../types/application';
 import { Visibility } from '../types/component';
index 8be0209fcad8e5a802641957b068184b7a673e43..baf8f9d9d7251ea38c5698b465cfdf4d124531aa 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post } from '../helpers/request';
 import { Branch, PullRequest } from '../types/branch-like';
 
index eea8235f8f657809f789594fdc74a5a23eb8c352..b91be651a4d13e0b7b6d101e6e22184d94c1ff6e 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post, RequestData } from '../helpers/request';
 import { IndexationStatus } from '../types/indexation';
 import { Task, TaskWarning } from '../types/tasks';
index 9bc734f2308e034c888b71a8d62ccb7efd2e3366..a3133b80adb4f5f497abb7f6ce74efe0d7b67a63 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post } from '../helpers/request';
 import { getBaseUrl } from '../helpers/system';
 import { ComponentReportStatus } from '../types/component-report';
index 3ebc6070a006b51c44af32b6bd7b9a52680c971f..eebd23b059f4340fa05ccef2f4cf8278ec45d8e2 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post, postJSON, RequestData } from '../helpers/request';
 import { BranchParameters } from '../types/branch-like';
 import { ComponentQualifier, TreeComponent, TreeComponentWithPath } from '../types/component';
diff --git a/server/sonar-web/src/main/js/api/editions.ts b/server/sonar-web/src/main/js/api/editions.ts
new file mode 100644 (file)
index 0000000..7a9d871
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { throwGlobalError } from '../helpers/error';
+import { getJSON } from '../helpers/request';
+import { License } from '../types/editions';
+
+export function isValidLicense(): Promise<{ isValidLicense: boolean }> {
+  return getJSON('/api/editions/is_valid_license');
+}
+
+export function showLicense(): Promise<License> {
+  return getJSON('/api/editions/show_license').catch((response: Response) => {
+    if (response && response.status === 404) {
+      return undefined;
+    }
+    return throwGlobalError(response);
+  });
+}
index 60fd262dbb554ffdc92200d7adb07245892212d4..b4e6509c10cac6139a8bab02c2a99ccc8b781a78 100644 (file)
@@ -17,8 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import throwGlobalError from '../app/utils/throwGlobalError';
 import getCoverageStatus from '../components/SourceViewer/helpers/getCoverageStatus';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post, postJSON, RequestData } from '../helpers/request';
 import { IssueResponse, RawIssuesResponse } from '../types/issues';
 import { Dict, FacetValue, IssueChangelog, SnippetsByComponent, SourceLine } from '../types/types';
index 4144301f796bea5a5ae7cd2339053d4fb85d0a5d..f6c39df6559ddf1f70e2e4e0479d5695469c408c 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { getJSON } from '../helpers/request';
-import { L10nBundleRequestParams, L10nBundleRequestResponse } from '../types/l10n';
+import { L10nBundleRequestParams, L10nBundleRequestResponse } from '../types/l10nBundle';
 
 export function fetchL10nBundle(
   params: L10nBundleRequestParams
index 941c2c4c7baf88e6f257d2969cd6f5f39618f3fe..45d891d948691a9119ce506b6188b906bee333e6 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON } from '../helpers/request';
 import { Language } from '../types/languages';
 
diff --git a/server/sonar-web/src/main/js/api/marketplace.ts b/server/sonar-web/src/main/js/api/marketplace.ts
deleted file mode 100644 (file)
index 6c376b3..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 throwGlobalError from '../app/utils/throwGlobalError';
-import { getJSON } from '../helpers/request';
-
-export interface License {
-  contactEmail: string;
-  edition: string;
-  expiresAt: string;
-  isExpired: boolean;
-  isOfficialDistribution: boolean;
-  isSupported: boolean;
-  isValidEdition: boolean;
-  isValidServerId: boolean;
-  loc: number;
-  maxLoc: number;
-  plugins: string[];
-  remainingLocThreshold: number;
-  serverId: string;
-  type: string;
-}
-
-export function isValidLicense(): Promise<{ isValidLicense: boolean }> {
-  return getJSON('/api/editions/is_valid_license');
-}
-
-export function showLicense(): Promise<License> {
-  return getJSON('/api/editions/show_license').catch((response: Response) => {
-    if (response && response.status === 404) {
-      return undefined;
-    }
-    return throwGlobalError(response);
-  });
-}
index 0387baf9f37c4c2ddfb2f504cb97e2c0ecffb2bf..de681a61a49a848ea6b9f415abcb9d42990d9170 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON } from '../helpers/request';
 import { BranchParameters } from '../types/branch-like';
 import {
index d2d5996b891a1c1996c247829f1d67324d56ec61..c3d684d774a4ebde632cfe0fab5ea90f3f5625a5 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON } from '../helpers/request';
 import { Metric } from '../types/types';
 
index 974fb5fc1b30e32b9a46600ebc3bf00b066ed390..bca04cabf9884ce5a6defc1b4fd7b9284a297130 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON } from '../helpers/request';
 import { BranchParameters } from '../types/branch-like';
 import { Component, Extension } from '../types/types';
index e33b3d297a453d6f322dbe1323fe725ff796b039..6e570e338aa50232aa0ddd763c8a74b027227187 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post } from '../helpers/request';
 import { NewCodePeriod, NewCodePeriodBranch, NewCodePeriodSettingType } from '../types/types';
 
index 27f0a2ad6a0e72a5d11c78882c2bd95d97aa86fa..79f51757d06c9c7c54c27a57c09ec0faa8ebb54d 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post } from '../helpers/request';
 import { Notification } from '../types/types';
 
index cb274e2e591434399ed5daef41955c9525fdb540..295ccc7f27436d2b1f26440c5776c6a89b22a898 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post, postJSON, RequestData } from '../helpers/request';
 import {
   Paging,
index abffaad12b67c89c905394d08e24719589b3162d..64510fd49e9393d2cfc7f1bd85720ffe96b75cc2 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { findLastIndex } from 'lodash';
-import throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post } from '../helpers/request';
 import { isDefined } from '../helpers/types';
 import {
index aa5110861b461b11a0af9a7c865205ea2fbeab95..44c9e94f34d761ae59018e8f1c445aa1a9542d59 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, postJSON } from '../helpers/request';
 
 export function getProjectBadgesToken(project: string) {
index 9e027dc9c47e686bd4851e3b9405107d8c545a7b..34e9271c7d456e96942b202a7e82d69a7a33b79f 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post } from '../helpers/request';
 import { DumpStatus } from '../types/project-dump';
 
index cc773eea128007e5e67ed19ce0ca23ec253f7dfa..9fde41ead19951f3765beee175a4d670c90dd216 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post, postJSON, RequestData } from '../helpers/request';
 import { BranchParameters } from '../types/branch-like';
 import { Analysis, Paging } from '../types/types';
index 7c7307d510b493769666bbb177b8134170c2f28f..6af46e1adc560edc7bf6e84f32e429326d2c5df8 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post, postJSON } from '../helpers/request';
 import { ProjectLink } from '../types/types';
 
index e28492899cd934cd665b4eae6c001c0a09feac70..dbc478b3b6018661dd4cbbdff71c2bb2a055fee0 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post, postJSON } from '../helpers/request';
 import { BranchParameters } from '../types/branch-like';
 import {
index ae130bb637358f5d36573e64bc27ab77226cfb33..283f0c52d2ac3476b0957ca11cefda63645fcaa7 100644 (file)
@@ -18,9 +18,9 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { map } from 'lodash';
-import throwGlobalError from '../app/utils/throwGlobalError';
 import { Exporter, ProfileChangelogEvent } from '../apps/quality-profiles/types';
 import { csvEscape } from '../helpers/csv';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post, postJSON, RequestData } from '../helpers/request';
 import { Dict, Paging, ProfileInheritanceDetails, UserSelected } from '../types/types';
 
index 419e7ed7eefca201c0bfd7a31dfdb52801cd6d1e..6a39950ed35051df406f50c0423154b617a03a35 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post, postJSON } from '../helpers/request';
 import { GetRulesAppResponse, SearchRulesResponse } from '../types/coding-rules';
 import { SearchRulesQuery } from '../types/rules';
index 2f2608b2b3b9503a5a9ef7aa094ac5e63b908083..b4d4d01dba0beaeb569ceb1e478502cef6991107 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post } from '../helpers/request';
 import { BranchParameters } from '../types/branch-like';
 import {
index e621314c27eee52da26b027bc107e34e2b35588f..e322511bce821c8fa727345449e9c2f74168e7dc 100644 (file)
@@ -18,8 +18,8 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { omitBy } from 'lodash';
-import throwGlobalError from '../app/utils/throwGlobalError';
 import { isCategoryDefinition } from '../apps/settings/utils';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post, postJSON, RequestData } from '../helpers/request';
 import { BranchParameters } from '../types/branch-like';
 import {
index 40b03839a31d22d4a73936cfd4087d623792a4a7..fa0721eb9b843a0a618c4080dfcc27602b406110 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post, postJSON, requestTryAndRepeatUntil } from '../helpers/request';
 import { SystemUpgrade } from '../types/system';
 import { SysInfoCluster, SysInfoStandalone, SysStatus } from '../types/types';
index 720e562738c1a454835e2547f542b6ecb9f477f7..f6e7ee3da68662fa3be14b9689178211b7b866d4 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON } from '../helpers/request';
 import { BranchParameters } from '../types/branch-like';
 import { Paging } from '../types/types';
index adcc92154757c7171d19e2cca1f496faff1a97c5..53daaf04b4b0e2b501a5924a0ec8764e5f6fddfb 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post, postJSON } from '../helpers/request';
 import { NewUserToken, UserToken } from '../types/types';
 
index 9b78402d12dfec1f7e7092560a096fba7e15a359..5949d53c70d7cb320ac14b00426a97c8acaca792 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post, postJSON } from '../helpers/request';
 import { Group, Paging, UserSelected } from '../types/types';
 
index 272c2c73295935ad358b72c26fd15c26d968afa2..9b94961ebc87f4a7cad678ad8cfc53f7a990c3ba 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post, postJSON } from '../helpers/request';
 import { IdentityProvider, Paging } from '../types/types';
 import { CurrentUser, HomePage, User } from '../types/users';
index b9d7119dde6128e5ecbaa95827359c1904622aa2..31b3770d7ac4b349ee254f4987c26573fc59a0fe 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON } from '../helpers/request';
 import { WebApi } from '../types/types';
 
index e178cc4dc02ede1d3b1d27c24c5bb68056ae7c72..3c9fe1300ef630b77476e21825b00070d4193935 100644 (file)
@@ -17,7 +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 throwGlobalError from '../app/utils/throwGlobalError';
+import { throwGlobalError } from '../helpers/error';
 import { getJSON, post, postJSON } from '../helpers/request';
 import { Paging, Webhook, WebhookDelivery } from '../types/types';
 
index ebbdebed5871a65b421995459b63462eec2d5460..8bf0201d3b4030e931cd1de76a2c28875f318111 100644 (file)
@@ -19,7 +19,7 @@
  */
 import { differenceInDays } from 'date-fns';
 import * as React from 'react';
-import { showLicense } from '../../api/marketplace';
+import { showLicense } from '../../api/editions';
 import { Location, Router, withRouter } from '../../components/hoc/withRouter';
 import { lazyLoadComponent } from '../../components/lazyLoadComponent';
 import { parseDate, toShortNotSoISOString } from '../../helpers/dates';
index 0813389c9ba0d4ce621002f306c7a889faafb8a4..c6d82ab38e9902bdecebc9f1b913e181aa77a013 100644 (file)
@@ -20,7 +20,7 @@
 import { differenceInDays } from 'date-fns';
 import { shallow, ShallowWrapper } from 'enzyme';
 import * as React from 'react';
-import { showLicense } from '../../../api/marketplace';
+import { showLicense } from '../../../api/editions';
 import { toShortNotSoISOString } from '../../../helpers/dates';
 import { hasMessage } from '../../../helpers/l10n';
 import { get, save } from '../../../helpers/storage';
@@ -30,7 +30,7 @@ import { EditionKey } from '../../../types/editions';
 import { LoggedInUser } from '../../../types/users';
 import { StartupModal } from '../StartupModal';
 
-jest.mock('../../../api/marketplace', () => ({
+jest.mock('../../../api/editions', () => ({
   showLicense: jest.fn().mockResolvedValue(undefined)
 }));
 
index 695cd5f3efcfb92a00bb2cc0f70ed26d178d831e..1220526fb6a5b07e0cfc96a46c9887e6023264dd 100644 (file)
@@ -23,7 +23,8 @@ import { injectIntl, WrappedComponentProps } from 'react-intl';
 import { connect } from 'react-redux';
 import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
 import { getExtensionStart } from '../../../helpers/extensions';
-import { getCurrentL10nBundle, translate } from '../../../helpers/l10n';
+import { translate } from '../../../helpers/l10n';
+import { getCurrentL10nBundle } from '../../../helpers/l10nBundle';
 import { getBaseUrl } from '../../../helpers/system';
 import { addGlobalErrorMessage } from '../../../store/globalMessages';
 import { AppState } from '../../../types/appstate';
index 243356b07ee8ebe697824a84bc8c1dd1046c1683..ab9847c117b052f23afefe6d21b00b848d36d77e 100644 (file)
@@ -87,6 +87,7 @@ import {
   isPullRequest,
   sortBranches
 } from '../../../helpers/branch-like';
+import { throwGlobalError } from '../../../helpers/error';
 import handleRequiredAuthentication from '../../../helpers/handleRequiredAuthentication';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import * as measures from '../../../helpers/measures';
@@ -119,7 +120,6 @@ import {
   getRulesUrl
 } from '../../../helpers/urls';
 import addGlobalSuccessMessage from '../../utils/addGlobalSuccessMessage';
-import throwGlobalError from '../../utils/throwGlobalError';
 import A11ySkipTarget from '../a11y/A11ySkipTarget';
 import Suggestions from '../embed-docs-modal/Suggestions';
 
index 7c9e217e1ab9f07d49cd0cd0e33adda6d2595c6e..7ada2c4db56e2997bd260c64064a314ebdca1c0f 100644 (file)
@@ -19,7 +19,7 @@
  */
 import * as React from 'react';
 import { Link } from 'react-router';
-import { isValidLicense } from '../../../../api/marketplace';
+import { isValidLicense } from '../../../../api/editions';
 import { Alert } from '../../../../components/ui/Alert';
 import { translate, translateWithParameters } from '../../../../helpers/l10n';
 import { AppState } from '../../../../types/appstate';
index 354a592c1794cad16460b8c5e796286ed23b27de..292790dfdce43d3e51bda2342bbbb50fd5215da4 100644 (file)
@@ -19,7 +19,7 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
-import { isValidLicense } from '../../../../../api/marketplace';
+import { isValidLicense } from '../../../../../api/editions';
 import { mockTask } from '../../../../../helpers/mocks/tasks';
 import { mockAppState } from '../../../../../helpers/testMocks';
 import { waitAndUpdate } from '../../../../../helpers/testUtils';
@@ -31,7 +31,7 @@ jest.mock('../../../../../helpers/l10n', () => ({
   hasMessage: jest.fn().mockReturnValue(true)
 }));
 
-jest.mock('../../../../../api/marketplace', () => ({
+jest.mock('../../../../../api/editions', () => ({
   isValidLicense: jest.fn().mockResolvedValue({ isValidLicense: false })
 }));
 
index f63328930f44b2605b2dc4ba23333674915c1143..85532d23a70343b7fd067d617270ecabd70a4cdb 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { installExtensionsHandler, installWebAnalyticsHandler } from '../helpers/extensionsHandler';
-import { loadL10nBundle } from '../helpers/l10n';
+import { loadL10nBundle } from '../helpers/l10nBundle';
 import { parseJSON, request } from '../helpers/request';
 import { getBaseUrl, getSystemStatus } from '../helpers/system';
 import { AppState } from '../types/appstate';
diff --git a/server/sonar-web/src/main/js/app/utils/__tests__/throwGlobalError-test.ts b/server/sonar-web/src/main/js/app/utils/__tests__/throwGlobalError-test.ts
deleted file mode 100644 (file)
index 13c5f9f..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 getStore from '../getStore';
-import throwGlobalError from '../throwGlobalError';
-
-beforeAll(() => {
-  jest.useFakeTimers();
-});
-
-afterAll(() => {
-  jest.runOnlyPendingTimers();
-  jest.useRealTimers();
-});
-
-it('should put the error message in the store', async () => {
-  const response = new Response();
-  response.json = jest.fn().mockResolvedValue({ errors: [{ msg: 'error 1' }] });
-
-  // We need to catch because throwGlobalError rethrows after displaying the message
-  await throwGlobalError(response)
-    .then(() => fail('Should throw'))
-    .catch(() => {});
-
-  expect(getStore().getState().globalMessages[0]).toMatchObject({
-    level: 'ERROR',
-    message: 'error 1'
-  });
-});
-
-it('should put a default error messsage in the store', async () => {
-  const response = new Response();
-  response.json = jest.fn().mockResolvedValue({});
-
-  // We need to catch because throwGlobalError rethrows after displaying the message
-  await throwGlobalError(response)
-    .then(() => fail('Should throw'))
-    .catch(() => {});
-
-  expect(getStore().getState().globalMessages[0]).toMatchObject({
-    level: 'ERROR',
-    message: 'default_error_message'
-  });
-});
-
-it('should handle weird response types', () => {
-  const response = { weird: 'response type' };
-
-  return throwGlobalError(response)
-    .then(() => fail('Should throw'))
-    .catch(error => {
-      expect(error).toBe(response);
-    });
-});
-
-it('should unwrap response if necessary', async () => {
-  const response = new Response();
-  response.json = jest.fn().mockResolvedValue({});
-
-  /* eslint-disable-next-line no-console */
-  console.warn = jest.fn();
-
-  // We need to catch because throwGlobalError rethrows after displaying the message
-  await throwGlobalError({ response })
-    .then(() => fail('Should throw'))
-    .catch(() => {});
-
-  /* eslint-disable-next-line no-console */
-  expect(console.warn).toHaveBeenCalled();
-});
diff --git a/server/sonar-web/src/main/js/app/utils/exportModulesAsGlobals.ts b/server/sonar-web/src/main/js/app/utils/exportModulesAsGlobals.ts
new file mode 100644 (file)
index 0000000..f9351d9
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 EmotionReact from '@emotion/react';
+import EmotionStyled from '@emotion/styled';
+import * as DateFns from 'date-fns';
+import Lodash from 'lodash';
+import React from 'react';
+import * as ReactDom from 'react-dom';
+import * as ReactIntl from 'react-intl';
+import ReactModal from 'react-modal';
+import * as ReactRouter from 'react-router';
+
+/*
+ * Expose dependencies to extensions
+ */
+export default function exportModulesAsGlobals() {
+  const w = (window as unknown) as any;
+  w.EmotionReact = EmotionReact;
+  w.EmotionStyled = EmotionStyled;
+  w.DateFns = DateFns;
+  w.Lodash = Lodash;
+  w.React = React;
+  w.ReactDOM = ReactDom;
+  w.ReactIntl = ReactIntl;
+  w.ReactModal = ReactModal;
+  w.ReactRouter = ReactRouter;
+}
index 0c8dc2ab1d500de289696ad8113eb55a138b7f32..fc17431e3fa6ca97f1f36c9736885cace2371e27 100644 (file)
@@ -21,7 +21,7 @@
 import { Location } from 'history';
 import { pick } from 'lodash';
 import * as React from 'react';
-import ReactDom, { render } from 'react-dom';
+import { render } from 'react-dom';
 import { HelmetProvider } from 'react-helmet-async';
 import { IntlProvider } from 'react-intl';
 import { Provider } from 'react-redux';
@@ -69,16 +69,9 @@ import GlobalContainer from '../components/GlobalContainer';
 import { PageContext } from '../components/indexation/PageUnavailableDueToIndexation';
 import MigrationContainer from '../components/MigrationContainer';
 import NonAdminPagesContainer from '../components/NonAdminPagesContainer';
+import exportModulesAsGlobals from './exportModulesAsGlobals';
 import getStore from './getStore';
 
-/*
- * Expose dependencies to extensions
- */
-function attachToGlobal() {
-  window.React = React;
-  window.ReactDOM = ReactDom;
-}
-
 function handleUpdate(this: { state: { location: Location } }) {
   const { action } = this.state.location;
 
@@ -202,7 +195,10 @@ function renderComponentRoutes() {
               if (query.types === 'SECURITY_HOTSPOT') {
                 replace({
                   pathname: '/security_hotspots',
-                  query: { ...pick(query, ['id', 'branch', 'pullRequest']), assignedToMe: false }
+                  query: {
+                    ...pick(query, ['id', 'branch', 'pullRequest']),
+                    assignedToMe: false
+                  }
                 });
               } else {
                 query.types = query.types
@@ -282,7 +278,7 @@ function renderAdminRoutes() {
 }
 
 export default function startReactApp(lang: string, appState: AppState, currentUser?: CurrentUser) {
-  attachToGlobal();
+  exportModulesAsGlobals();
 
   const el = document.getElementById('content');
 
diff --git a/server/sonar-web/src/main/js/app/utils/throwGlobalError.ts b/server/sonar-web/src/main/js/app/utils/throwGlobalError.ts
deleted file mode 100644 (file)
index dbb9ee4..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { parseError } from '../../helpers/request';
-import { addGlobalErrorMessage } from '../../store/globalMessages';
-import getStore from './getStore';
-
-export default function throwGlobalError(param: Response | any): Promise<Response | any> {
-  const store = getStore();
-
-  if (param.response instanceof Response) {
-    /* eslint-disable-next-line no-console */
-    console.warn('DEPRECATED: response should not be wrapped, pass it directly.');
-    param = param.response;
-  }
-
-  if (param instanceof Response) {
-    return parseError(param)
-      .then(
-        message => {
-          store.dispatch(addGlobalErrorMessage(message));
-        },
-        () => {}
-      )
-      .then(() => Promise.reject(param));
-  }
-
-  return Promise.reject(param);
-}
index 11dab834f4b4613f6d96cabc286c05e31fe273c8..7fe3dc6a97866bf5ab9d853598bc8599875319f1 100644 (file)
@@ -5,6 +5,7 @@ exports[`should display facet item list 1`] = `
   property="Reliability"
 >
   <FacetHeader
+    helper="component_measures.domain_facets.Reliability.help"
     name="Reliability"
     onClick={[Function]}
     open={true}
@@ -141,6 +142,7 @@ exports[`should display facet item list with bugs selected 1`] = `
   property="Reliability"
 >
   <FacetHeader
+    helper="component_measures.domain_facets.Reliability.help"
     name="Reliability"
     onClick={[Function]}
     open={true}
@@ -281,6 +283,7 @@ exports[`should not display subtitles of new measures if there is none 1`] = `
   property="Reliability"
 >
   <FacetHeader
+    helper="component_measures.domain_facets.Reliability.help"
     name="Reliability"
     onClick={[Function]}
     open={true}
@@ -363,6 +366,7 @@ exports[`should not display subtitles of new measures if there is none, even on
   property="Reliability"
 >
   <FacetHeader
+    helper="component_measures.domain_facets.Reliability.help"
     name="Reliability"
     onClick={[Function]}
     open={true}
index fe7516661181cb6944540071462b6c781512461b..0690a1223a973fc45ee6699db1e66d0415badc73 100644 (file)
@@ -21,7 +21,6 @@ import { pickBy, sortBy } from 'lodash';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { bulkChangeIssues, searchIssueTags } from '../../../api/issues';
-import throwGlobalError from '../../../app/utils/throwGlobalError';
 import FormattingTips from '../../../components/common/FormattingTips';
 import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
 import Checkbox from '../../../components/controls/Checkbox';
@@ -35,6 +34,7 @@ import IssueTypeIcon from '../../../components/icons/IssueTypeIcon';
 import SeverityHelper from '../../../components/shared/SeverityHelper';
 import { Alert } from '../../../components/ui/Alert';
 import Avatar from '../../../components/ui/Avatar';
+import { throwGlobalError } from '../../../helpers/error';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { Component, Dict, Issue, IssueType, Paging } from '../../../types/types';
 import { CurrentUser, isLoggedIn, isUserActive } from '../../../types/users';
index ed64d641e5f7560871ee52dc1cea1c0493f53ce9..2bd9406c4106c29c307ed5fee7f4c12321b2a6f5 100644 (file)
@@ -21,7 +21,6 @@ import { findLastIndex } from 'lodash';
 import * as React from 'react';
 import { getDuplications } from '../../../api/components';
 import { getIssueFlowSnippets } from '../../../api/issues';
-import throwGlobalError from '../../../app/utils/throwGlobalError';
 import DuplicationPopup from '../../../components/SourceViewer/components/DuplicationPopup';
 import {
   filterDuplicationBlocksByLine,
@@ -37,6 +36,7 @@ import { Alert } from '../../../components/ui/Alert';
 import DeferredSpinner from '../../../components/ui/DeferredSpinner';
 import { WorkspaceContext } from '../../../components/workspace/context';
 import { getBranchLikeQuery } from '../../../helpers/branch-like';
+import { throwGlobalError } from '../../../helpers/error';
 import { translate } from '../../../helpers/l10n';
 import { BranchLike } from '../../../types/branch-like';
 import {
index a7a8493f2cdca38e8b9e74ace6e40ec853ecaca5..72e28f5ac3888a022e1ce19154663c39816ed330 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 { resetMessages } from '../../../helpers/l10n';
 import { isSonarCloud } from '../../../helpers/system';
 import { convertToPermissionDefinitions } from '../utils';
 
 jest.mock('../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
 
-afterEach(() => {
-  resetMessages({});
-});
+jest.mock('../../../helpers/l10nBundle', () => ({
+  getMessages: jest.fn().mockReturnValue({})
+}));
 
 describe('convertToPermissionDefinitions', () => {
   it('should convert and translate a permission definition', () => {
     (isSonarCloud as jest.Mock).mockImplementation(() => false);
 
-    resetMessages({
-      'global_permissions.admin': 'Administer System'
-    });
-
     const data = convertToPermissionDefinitions(['admin'], 'global_permissions');
     const expected = [
-      { description: 'global_permissions.admin.desc', key: 'admin', name: 'Administer System' }
+      {
+        description: 'global_permissions.admin.desc',
+        key: 'admin',
+        name: 'global_permissions.admin'
+      }
     ];
 
     expect(data).toEqual(expected);
@@ -46,17 +45,12 @@ describe('convertToPermissionDefinitions', () => {
   it('should convert and translate a permission definition for SonarCloud', () => {
     (isSonarCloud as jest.Mock).mockImplementation(() => true);
 
-    resetMessages({
-      'global_permissions.admin': 'Administer System',
-      'global_permissions.admin.sonarcloud': 'Administer Organization'
-    });
-
     const data = convertToPermissionDefinitions(['admin'], 'global_permissions');
     const expected = [
       {
-        description: 'global_permissions.admin.desc',
+        description: 'global_permissions.admin.desc.sonarcloud',
         key: 'admin',
-        name: 'Administer Organization'
+        name: 'global_permissions.admin.sonarcloud'
       }
     ];
 
@@ -66,13 +60,13 @@ describe('convertToPermissionDefinitions', () => {
   it('should fallback to basic message when SonarCloud version does not exist', () => {
     (isSonarCloud as jest.Mock).mockImplementation(() => true);
 
-    resetMessages({
-      'global_permissions.admin': 'Administer System'
-    });
-
     const data = convertToPermissionDefinitions(['admin'], 'global_permissions');
     const expected = [
-      { description: 'global_permissions.admin.desc', key: 'admin', name: 'Administer System' }
+      {
+        description: 'global_permissions.admin.desc.sonarcloud',
+        key: 'admin',
+        name: 'global_permissions.admin.sonarcloud'
+      }
     ];
 
     expect(data).toEqual(expected);
index 29043ef9a850c5ff0625e725c71f10410f32b54a..43c17531ec2b2530e114fe9bdb5d8c54760fe183 100644 (file)
@@ -21,7 +21,7 @@ import * as React from 'react';
 import { getActivity } from '../../api/ce';
 import { getStatus } from '../../api/project-dump';
 import withAppStateContext from '../../app/components/app-state/withAppStateContext';
-import throwGlobalError from '../../app/utils/throwGlobalError';
+import { throwGlobalError } from '../../helpers/error';
 import { translate } from '../../helpers/l10n';
 import { AppState } from '../../types/appstate';
 import { DumpStatus, DumpTask } from '../../types/project-dump';
index bc199c6498d031e7cfacd57c502225f484a91bbf..1b46b821b3fa49b4303ad8579a75e0df85f3a56e 100644 (file)
@@ -27,7 +27,7 @@ exports[`should render correctly: branches disabled 1`] = `
   >
     <IndexLink
       className=""
-      title="general"
+      title="property.category.general"
       to={
         Object {
           "pathname": "/admin/settings",
@@ -37,7 +37,7 @@ exports[`should render correctly: branches disabled 1`] = `
         }
       }
     >
-      general
+      property.category.general
     </IndexLink>
   </li>
 </ul>
@@ -88,7 +88,7 @@ exports[`should render correctly: global mode 1`] = `
   >
     <IndexLink
       className=""
-      title="general"
+      title="property.category.general"
       to={
         Object {
           "pathname": "/admin/settings",
@@ -98,7 +98,7 @@ exports[`should render correctly: global mode 1`] = `
         }
       }
     >
-      general
+      property.category.general
     </IndexLink>
   </li>
 </ul>
@@ -151,7 +151,7 @@ exports[`should render correctly: project mode 1`] = `
   >
     <IndexLink
       className=""
-      title="general"
+      title="property.category.general"
       to={
         Object {
           "pathname": "/project/settings",
@@ -162,7 +162,7 @@ exports[`should render correctly: project mode 1`] = `
         }
       }
     >
-      general
+      property.category.general
     </IndexLink>
   </li>
 </ul>
@@ -213,7 +213,7 @@ exports[`should render correctly: selected category 1`] = `
   >
     <IndexLink
       className=""
-      title="general"
+      title="property.category.general"
       to={
         Object {
           "pathname": "/admin/settings",
@@ -223,7 +223,7 @@ exports[`should render correctly: selected category 1`] = `
         }
       }
     >
-      general
+      property.category.general
     </IndexLink>
   </li>
 </ul>
index 25ae668483f318a7669e5d1b4560e7d7214fbaf1..4dd0f528a50531c13c9fb849481b763bbea35e52 100644 (file)
@@ -10,6 +10,17 @@ exports[`should render correctly: changed value 1`] = `
   >
     <h3
       className="settings-definition-name"
+      title="property.foo.name"
+    >
+      property.foo.name
+    </h3>
+    <div
+      className="markdown small spacer-top"
+      dangerouslySetInnerHTML={
+        Object {
+          "__html": "property.foo.description",
+        }
+      }
     />
     <div
       className="settings-definition-key note little-spacer-top"
@@ -83,6 +94,17 @@ exports[`should render correctly: in error 1`] = `
   >
     <h3
       className="settings-definition-name"
+      title="property.foo.name"
+    >
+      property.foo.name
+    </h3>
+    <div
+      className="markdown small spacer-top"
+      dangerouslySetInnerHTML={
+        Object {
+          "__html": "property.foo.description",
+        }
+      }
     />
     <div
       className="settings-definition-key note little-spacer-top"
@@ -165,6 +187,17 @@ exports[`should render correctly: loading 1`] = `
   >
     <h3
       className="settings-definition-name"
+      title="property.foo.name"
+    >
+      property.foo.name
+    </h3>
+    <div
+      className="markdown small spacer-top"
+      dangerouslySetInnerHTML={
+        Object {
+          "__html": "property.foo.description",
+        }
+      }
     />
     <div
       className="settings-definition-key note little-spacer-top"
@@ -245,6 +278,17 @@ exports[`should render correctly: original value 1`] = `
   >
     <h3
       className="settings-definition-name"
+      title="property.foo.name"
+    >
+      property.foo.name
+    </h3>
+    <div
+      className="markdown small spacer-top"
+      dangerouslySetInnerHTML={
+        Object {
+          "__html": "property.foo.description",
+        }
+      }
     />
     <div
       className="settings-definition-key note little-spacer-top"
@@ -319,6 +363,17 @@ exports[`should render correctly: success 1`] = `
   >
     <h3
       className="settings-definition-name"
+      title="property.foo.name"
+    >
+      property.foo.name
+    </h3>
+    <div
+      className="markdown small spacer-top"
+      dangerouslySetInnerHTML={
+        Object {
+          "__html": "property.foo.description",
+        }
+      }
     />
     <div
       className="settings-definition-key note little-spacer-top"
@@ -399,12 +454,15 @@ exports[`should render correctly: with description 1`] = `
   >
     <h3
       className="settings-definition-name"
-    />
+      title="property.foo.name"
+    >
+      property.foo.name
+    </h3>
     <div
       className="markdown small spacer-top"
       dangerouslySetInnerHTML={
         Object {
-          "__html": "description",
+          "__html": "property.foo.description",
         }
       }
     />
index 71ac56dcb64a969fa6a7e126fbeddaf482d5f536..0659334976a25e6d7adf720c2ec19dd8f2bb6d14 100644 (file)
@@ -16,17 +16,17 @@ exports[`should render correctly 1`] = `
       options={
         Array [
           Object {
-            "label": "Java",
+            "label": "property.category.Java",
             "originalValue": "Java",
             "value": "java",
           },
           Object {
-            "label": "JavaScript",
+            "label": "property.category.JavaScript",
             "originalValue": "JavaScript",
             "value": "javascript",
           },
           Object {
-            "label": "COBOL",
+            "label": "property.category.COBOL",
             "originalValue": "COBOL",
             "value": "cobol",
           },
@@ -63,17 +63,17 @@ exports[`should render correctly with an unknow language 1`] = `
       options={
         Array [
           Object {
-            "label": "Java",
+            "label": "property.category.Java",
             "originalValue": "Java",
             "value": "java",
           },
           Object {
-            "label": "JavaScript",
+            "label": "property.category.JavaScript",
             "originalValue": "JavaScript",
             "value": "javascript",
           },
           Object {
-            "label": "COBOL",
+            "label": "property.category.COBOL",
             "originalValue": "COBOL",
             "value": "cobol",
           },
index ff66d64f6a40bcf6b22facce344d41bcc77961db..d4699f7f8e247bb99bf3779cd81af45bebc0c4a0 100644 (file)
@@ -11,8 +11,16 @@ exports[`should render correctly 1`] = `
       className="settings-sub-category-name"
       data-key="email"
     >
-      email
+      property.category.general.email
     </h2>
+    <div
+      className="settings-sub-category-description markdown"
+      dangerouslySetInnerHTML={
+        Object {
+          "__html": "property.category.general.email.description",
+        }
+      }
+    />
     <DefinitionsList
       scrollToDefinition={[Function]}
       settings={
@@ -45,8 +53,16 @@ exports[`should render correctly 1`] = `
       className="settings-sub-category-name"
       data-key="qg"
     >
-      qg
+      property.category.general.qg
     </h2>
+    <div
+      className="settings-sub-category-description markdown"
+      dangerouslySetInnerHTML={
+        Object {
+          "__html": "property.category.general.qg.description",
+        }
+      }
+    />
     <DefinitionsList
       scrollToDefinition={[Function]}
       settings={
@@ -83,8 +99,16 @@ exports[`should render correctly: subcategory 1`] = `
       className="settings-sub-category-name"
       data-key="qg"
     >
-      qg
+      property.category.general.qg
     </h2>
+    <div
+      className="settings-sub-category-description markdown"
+      dangerouslySetInnerHTML={
+        Object {
+          "__html": "property.category.general.qg.description",
+        }
+      }
+    />
     <DefinitionsList
       scrollToDefinition={[Function]}
       settings={
index dcd438581573dda5ea7d76eb4e8607903bde574a..5d307e589cc5dec2b59eadfa989173d4e3cbf9ce 100644 (file)
@@ -30,7 +30,7 @@ import {
   validateProjectAlmBinding
 } from '../../../../api/alm-settings';
 import withCurrentUserContext from '../../../../app/components/current-user/withCurrentUserContext';
-import throwGlobalError from '../../../../app/utils/throwGlobalError';
+import { throwGlobalError } from '../../../../helpers/error';
 import { HttpStatus } from '../../../../helpers/request';
 import { hasGlobalPermission } from '../../../../helpers/users';
 import {
index b4b0d24b7766b6f61c26ff1fd854735124933d4b..86f9416a7fe148b8eb76890a57648e764420918f 100644 (file)
 import * as React from 'react';
 import { changePassword } from '../../../api/users';
 import addGlobalSuccessMessage from '../../../app/utils/addGlobalSuccessMessage';
-import throwGlobalError from '../../../app/utils/throwGlobalError';
 import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
 import Modal from '../../../components/controls/Modal';
 import { Alert } from '../../../components/ui/Alert';
 import MandatoryFieldMarker from '../../../components/ui/MandatoryFieldMarker';
 import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
+import { throwGlobalError } from '../../../helpers/error';
 import { translate } from '../../../helpers/l10n';
 import { parseError } from '../../../helpers/request';
 import { User } from '../../../types/users';
index e0d954293d63203354bb5f4472577df4cb833e0d..3bd0a8cc0e9209dcc8dcb607e0a43043b4049301 100644 (file)
 import { uniq } from 'lodash';
 import * as React from 'react';
 import { createUser, updateUser } from '../../../api/users';
-import throwGlobalError from '../../../app/utils/throwGlobalError';
 import { Button, ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
 import SimpleModal from '../../../components/controls/SimpleModal';
 import { Alert } from '../../../components/ui/Alert';
 import MandatoryFieldMarker from '../../../components/ui/MandatoryFieldMarker';
 import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
+import { throwGlobalError } from '../../../helpers/error';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { parseError } from '../../../helpers/request';
 import { User } from '../../../types/users';
index 93d18d556ffe26d8a8dbb13c46f295a816d2811a..fb0a525465ba5f54d813b414e07b703ce80f33b6 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 throwGlobalError from '../../../app/utils/throwGlobalError';
+import { throwGlobalError } from '../../../helpers/error';
 import { parseIssueFromResponse } from '../../../helpers/issues';
 import { mockComponent } from '../../../helpers/mocks/component';
 import { mockIssue } from '../../../helpers/testMocks';
 import { updateIssue } from '../actions';
 
-jest.mock('../../../app/utils/throwGlobalError', () => jest.fn());
+jest.mock('../../../helpers/error', () => ({ throwGlobalError: jest.fn() }));
 
 jest.mock('../../../helpers/issues', () => ({
   parseIssueFromResponse: jest.fn()
index c074dc0c95259f9afc7749587e43b8befc3fb7e3..e84b3a5fcd7cf90d87c37d2a1bd75af1990d0082 100644 (file)
@@ -17,7 +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 throwGlobalError from '../../app/utils/throwGlobalError';
+import { throwGlobalError } from '../../helpers/error';
 import { parseIssueFromResponse } from '../../helpers/issues';
 import { IssueResponse } from '../../types/issues';
 import { Issue } from '../../types/types';
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/error-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/error-test.ts
new file mode 100644 (file)
index 0000000..ca6f81a
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 getStore from '../../app/utils/getStore';
+import { throwGlobalError } from '../error';
+
+beforeAll(() => {
+  jest.useFakeTimers();
+});
+
+afterAll(() => {
+  jest.runOnlyPendingTimers();
+  jest.useRealTimers();
+});
+
+it('should put the error message in the store', async () => {
+  const response = new Response();
+  response.json = jest.fn().mockResolvedValue({ errors: [{ msg: 'error 1' }] });
+
+  // We need to catch because throwGlobalError rethrows after displaying the message
+  await throwGlobalError(response)
+    .then(() => {
+      throw new Error('Should throw');
+    })
+    .catch(() => {});
+
+  expect(getStore().getState().globalMessages[0]).toMatchObject({
+    level: 'ERROR',
+    message: 'error 1'
+  });
+});
+
+it('should put a default error messsage in the store', async () => {
+  const response = new Response();
+  response.json = jest.fn().mockResolvedValue({});
+
+  // We need to catch because throwGlobalError rethrows after displaying the message
+  await throwGlobalError(response)
+    .then(() => {
+      throw new Error('Should throw');
+    })
+    .catch(() => {});
+
+  expect(getStore().getState().globalMessages[0]).toMatchObject({
+    level: 'ERROR',
+    message: 'default_error_message'
+  });
+});
+
+it('should handle weird response types', () => {
+  const response = { weird: 'response type' };
+
+  return throwGlobalError(response)
+    .then(() => {
+      throw new Error('Should throw');
+    })
+    .catch(error => {
+      expect(error).toBe(response);
+    });
+});
+
+it('should unwrap response if necessary', async () => {
+  const response = new Response();
+  response.json = jest.fn().mockResolvedValue({});
+
+  /* eslint-disable-next-line no-console */
+  console.warn = jest.fn();
+
+  // We need to catch because throwGlobalError rethrows after displaying the message
+  await throwGlobalError({ response })
+    .then(() => {
+      throw new Error('Should throw');
+    })
+    .catch(() => {});
+
+  /* eslint-disable-next-line no-console */
+  expect(console.warn).toHaveBeenCalled();
+});
index 58d2f94461d87599f6f99e7f7dcd6adba081c22a..346d097dce8071221b71bb5d9bb4c895b6cb3223 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 { fetchL10nBundle } from '../../api/l10n';
+import { Dict } from '../../types/types';
 import {
   getLocalizedCategoryMetricName,
   getLocalizedMetricDomain,
   getLocalizedMetricName,
-  getMessages,
   getShortMonthName,
   getShortWeekDayName,
   getWeekDayName,
   hasMessage,
-  loadL10nBundle,
-  resetMessages,
   translate,
   translateWithParameters
 } from '../l10n';
-import { get } from '../storage';
+import { getMessages } from '../l10nBundle';
 
-beforeEach(() => {
-  jest.clearAllMocks();
-  jest.spyOn(window.navigator, 'languages', 'get').mockReturnValue(['de']);
-});
+const MSG = 'my_message';
 
-jest.mock('../../api/l10n', () => ({
-  fetchL10nBundle: jest
-    .fn()
-    .mockResolvedValue({ effectiveLocale: 'de', messages: { test_message: 'test' } })
-}));
+jest.unmock('../l10n');
 
-jest.mock('../../helpers/storage', () => ({
-  get: jest.fn(),
-  save: jest.fn()
+jest.mock('../l10nBundle', () => ({
+  getMessages: jest.fn().mockReturnValue({})
 }));
 
-describe('#loadL10nBundle', () => {
-  it('should fetch bundle without any timestamp', async () => {
-    await loadL10nBundle();
-
-    expect(fetchL10nBundle).toHaveBeenCalledWith({ locale: 'de', ts: undefined });
-  });
-
-  it('should ftech bundle without local storage timestamp if locales are different', async () => {
-    const cachedBundle = { timestamp: 'timestamp', locale: 'fr', messages: { cache: 'cache' } };
-    (get as jest.Mock).mockReturnValueOnce(JSON.stringify(cachedBundle));
-
-    await loadL10nBundle();
-
-    expect(fetchL10nBundle).toHaveBeenCalledWith({ locale: 'de', ts: undefined });
-  });
-
-  it('should fetch bundle with cached bundle timestamp and browser locale', async () => {
-    const cachedBundle = { timestamp: 'timestamp', locale: 'de', messages: { cache: 'cache' } };
-    (get as jest.Mock).mockReturnValueOnce(JSON.stringify(cachedBundle));
+const resetMessages = (messages: Dict<string>) =>
+  (getMessages as jest.Mock).mockReturnValue(messages);
 
-    await loadL10nBundle();
+beforeEach(() => {
+  resetMessages({});
+});
 
-    expect(fetchL10nBundle).toHaveBeenCalledWith({ locale: 'de', ts: cachedBundle.timestamp });
+describe('hasMessage', () => {
+  it('should return that the message exists', () => {
+    resetMessages({
+      foo: 'foo',
+      'foo.bar': 'foobar'
+    });
+    expect(hasMessage('foo')).toBe(true);
+    expect(hasMessage('foo', 'bar')).toBe(true);
   });
 
-  it('should fallback to cached bundle if the server respond with 304', async () => {
-    const cachedBundle = { timestamp: 'timestamp', locale: 'fr', messages: { cache: 'cache' } };
-    (fetchL10nBundle as jest.Mock).mockRejectedValueOnce({ status: 304 });
-    (get as jest.Mock).mockReturnValueOnce(JSON.stringify(cachedBundle));
-
-    const bundle = await loadL10nBundle();
-
-    expect(bundle).toEqual(
-      expect.objectContaining({ locale: cachedBundle.locale, messages: cachedBundle.messages })
-    );
+  it('should return that the message is missing', () => {
+    expect(hasMessage('foo')).toBe(false);
+    expect(hasMessage('foo', 'bar')).toBe(false);
   });
 });
 
-const originalMessages = getMessages();
-const MSG = 'my_message';
-
-afterEach(() => {
-  resetMessages(originalMessages);
-});
-
 describe('translate', () => {
   it('should translate simple message', () => {
     resetMessages({ my_key: MSG });
@@ -153,19 +120,6 @@ describe('translateWithParameters', () => {
   });
 });
 
-describe('hasMessage', () => {
-  it('should return that the message exists', () => {
-    resetMessages({ foo: 'Foo', 'foo.bar': 'Foo Bar' });
-    expect(hasMessage('foo')).toBe(true);
-    expect(hasMessage('foo', 'bar')).toBe(true);
-  });
-
-  it('should return that the message is missing', () => {
-    expect(hasMessage('foo')).toBe(false);
-    expect(hasMessage('foo', 'bar')).toBe(false);
-  });
-});
-
 describe('getLocalizedMetricName', () => {
   const metric = { key: 'new_code', name: 'new_code_metric_name' };
 
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/l10nBundle-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/l10nBundle-test.ts
new file mode 100644 (file)
index 0000000..58c1e60
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { fetchL10nBundle } from '../../api/l10n';
+import { loadL10nBundle } from '../l10nBundle';
+
+beforeEach(() => {
+  jest.clearAllMocks();
+  jest.spyOn(window.navigator, 'languages', 'get').mockReturnValue(['de']);
+});
+
+jest.mock('../../api/l10n', () => ({
+  fetchL10nBundle: jest.fn().mockResolvedValue({
+    effectiveLocale: 'de',
+    messages: { foo: 'Foo', 'foo.bar': 'Foo Bar' }
+  })
+}));
+
+describe('#loadL10nBundle', () => {
+  it('should fetch bundle without any timestamp', async () => {
+    await loadL10nBundle();
+
+    expect(fetchL10nBundle).toHaveBeenCalledWith({ locale: 'de', ts: undefined });
+  });
+
+  it('should ftech bundle without local storage timestamp if locales are different', async () => {
+    const cachedBundle = { timestamp: 'timestamp', locale: 'fr', messages: { cache: 'cache' } };
+    ((window as unknown) as any).sonarQubeL10nBundle = cachedBundle;
+
+    await loadL10nBundle();
+
+    expect(fetchL10nBundle).toHaveBeenCalledWith({ locale: 'de', ts: undefined });
+  });
+
+  it('should fetch bundle with cached bundle timestamp and browser locale', async () => {
+    const cachedBundle = { timestamp: 'timestamp', locale: 'de', messages: { cache: 'cache' } };
+    ((window as unknown) as any).sonarQubeL10nBundle = cachedBundle;
+
+    await loadL10nBundle();
+
+    expect(fetchL10nBundle).toHaveBeenCalledWith({ locale: 'de', ts: cachedBundle.timestamp });
+  });
+
+  it('should fallback to cached bundle if the server respond with 304', async () => {
+    const cachedBundle = { timestamp: 'timestamp', locale: 'fr', messages: { cache: 'cache' } };
+    (fetchL10nBundle as jest.Mock).mockRejectedValueOnce({ status: 304 });
+    ((window as unknown) as any).sonarQubeL10nBundle = cachedBundle;
+
+    const bundle = await loadL10nBundle();
+
+    expect(bundle).toEqual(
+      expect.objectContaining({ locale: cachedBundle.locale, messages: cachedBundle.messages })
+    );
+  });
+});
index 04a0bfa2ffa96666c20398f33b1f1405016b3b05..3a301c9b9b69df40c3a4358d935e0960402969bf 100644 (file)
@@ -17,7 +17,9 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { resetMessages } from '../l10n';
+
+import { Dict } from '../../types/types';
+import { getMessages } from '../l10nBundle';
 import {
   enhanceConditionWithMeasure,
   formatMeasure,
@@ -27,6 +29,36 @@ import {
 import { mockQualityGateStatusCondition } from '../mocks/quality-gates';
 import { mockMeasureEnhanced, mockMetric } from '../testMocks';
 
+jest.unmock('../l10n');
+
+jest.mock('../l10nBundle', () => ({
+  getCurrentLocale: jest.fn().mockReturnValue('us'),
+  getMessages: jest.fn().mockReturnValue({})
+}));
+
+const resetMessages = (messages: Dict<string>) =>
+  (getMessages as jest.Mock).mockReturnValue(messages);
+
+beforeAll(() => {
+  resetMessages({
+    'work_duration.x_days': '{0}d',
+    'work_duration.x_hours': '{0}h',
+    'work_duration.x_minutes': '{0}min',
+    'work_duration.about': '~ {0}',
+    'metric.level.ERROR': 'Error',
+    'metric.level.WARN': 'Warning',
+    'metric.level.OK': 'Ok',
+    'short_number_suffix.g': 'G',
+    'short_number_suffix.k': 'k',
+    'short_number_suffix.m': 'M'
+  });
+});
+
+const HOURS_IN_DAY = 8;
+const ONE_MINUTE = 1;
+const ONE_HOUR = ONE_MINUTE * 60;
+const ONE_DAY = HOURS_IN_DAY * ONE_HOUR;
+
 describe('enhanceConditionWithMeasure', () => {
   it('should correctly map enhance conditions with measure data', () => {
     const measures = [
@@ -71,30 +103,6 @@ describe('isPeriodBestValue', () => {
   });
 });
 
-const HOURS_IN_DAY = 8;
-const ONE_MINUTE = 1;
-const ONE_HOUR = ONE_MINUTE * 60;
-const ONE_DAY = HOURS_IN_DAY * ONE_HOUR;
-
-beforeAll(() => {
-  resetMessages({
-    'work_duration.x_days': '{0}d',
-    'work_duration.x_hours': '{0}h',
-    'work_duration.x_minutes': '{0}min',
-    'work_duration.about': '~ {0}',
-    'metric.level.ERROR': 'Error',
-    'metric.level.WARN': 'Warning',
-    'metric.level.OK': 'Ok',
-    'short_number_suffix.g': 'G',
-    'short_number_suffix.k': 'k',
-    'short_number_suffix.m': 'M'
-  });
-});
-
-afterAll(() => {
-  resetMessages({});
-});
-
 describe('#formatMeasure()', () => {
   it('should format INT', () => {
     expect(formatMeasure(0, 'INT')).toBe('0');
diff --git a/server/sonar-web/src/main/js/helpers/error.ts b/server/sonar-web/src/main/js/helpers/error.ts
new file mode 100644 (file)
index 0000000..78f2fa1
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 getStore from '../app/utils/getStore';
+import { addGlobalErrorMessage } from '../store/globalMessages';
+import { parseError } from './request';
+
+export function throwGlobalError(param: Response | any): Promise<Response | any> {
+  const store = getStore();
+
+  if (param.response instanceof Response) {
+    /* eslint-disable-next-line no-console */
+    console.warn('DEPRECATED: response should not be wrapped, pass it directly.');
+    param = param.response;
+  }
+
+  if (param instanceof Response) {
+    return parseError(param)
+      .then(
+        message => {
+          store.dispatch(addGlobalErrorMessage(message));
+        },
+        () => {}
+      )
+      .then(() => Promise.reject(param));
+  }
+
+  return Promise.reject(param);
+}
index 9b8fafa258e881e2e407604e87f4a6b20da6b141..149955f284474c3694a648f01ea22bfce60e590d 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 { fetchL10nBundle } from '../api/l10n';
-import { L10nBundle, L10nBundleRequestParams } from '../types/l10n';
-import { Dict } from '../types/types';
-import { toNotSoISOString } from './dates';
-import { get as loadFromLocalStorage, save as saveInLocalStorage } from './storage';
 
-export type Messages = Dict<string>;
+import { getMessages } from './l10nBundle';
 
-export const DEFAULT_LOCALE = 'en';
-export const DEFAULT_MESSAGES = {
-  // eslint-disable-next-line camelcase
-  default_error_message: 'The request cannot be processed. Try again later.'
-};
-
-let allMessages: Messages = {};
-let locale: string | undefined;
+export function hasMessage(...keys: string[]): boolean {
+  const messageKey = keys.join('.');
+  return getMessages()[messageKey] != null;
+}
 
 export function translate(...keys: string[]): string {
   const messageKey = keys.join('.');
@@ -61,23 +52,6 @@ export function translateWithParameters(
   return `${messageKey}.${parameters.join('.')}`;
 }
 
-export function hasMessage(...keys: string[]): boolean {
-  const messageKey = keys.join('.');
-  return getMessages()[messageKey] != null;
-}
-
-export function getMessages() {
-  if (typeof allMessages === 'undefined') {
-    logWarning('L10n messages are not initialized. Use default messages.');
-    return DEFAULT_MESSAGES;
-  }
-  return allMessages;
-}
-
-export function resetMessages(newMessages: Messages) {
-  allMessages = newMessages;
-}
-
 export function getLocalizedMetricName(
   metric: { key: string; name?: string },
   short = false
@@ -101,14 +75,6 @@ export function getLocalizedMetricDomain(domainName: string) {
   return hasMessage(bundleKey) ? translate(bundleKey) : domainName;
 }
 
-export function getCurrentLocale() {
-  return locale;
-}
-
-export function resetCurrentLocale(newLocale: string) {
-  locale = newLocale;
-}
-
 export function getShortMonthName(index: number) {
   const months = [
     'Jan',
@@ -136,88 +102,3 @@ export function getShortWeekDayName(index: number) {
   const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
   return weekdays[index] ? translate(weekdays[index]) : '';
 }
-
-const L10N_BUNDLE_LS_KEY = 'l10n.bundle';
-
-export async function loadL10nBundle() {
-  const bundle = await getLatestL10nBundle().catch(() => ({
-    locale: DEFAULT_LOCALE,
-    messages: {}
-  }));
-
-  resetCurrentLocale(bundle.locale);
-  resetMessages(bundle.messages);
-
-  return bundle;
-}
-
-export async function getLatestL10nBundle() {
-  const browserLocale = getPreferredLanguage();
-  const cachedBundle = loadL10nBundleFromLocalStorage();
-
-  const params: L10nBundleRequestParams = {};
-
-  if (browserLocale) {
-    params.locale = browserLocale;
-
-    if (
-      cachedBundle.locale &&
-      browserLocale.startsWith(cachedBundle.locale) &&
-      cachedBundle.timestamp &&
-      cachedBundle.messages
-    ) {
-      params.ts = cachedBundle.timestamp;
-    }
-  }
-
-  const { effectiveLocale, messages } = await fetchL10nBundle(params).catch(response => {
-    if (response && response.status === 304) {
-      return {
-        effectiveLocale: cachedBundle.locale || browserLocale || DEFAULT_LOCALE,
-        messages: cachedBundle.messages ?? {}
-      };
-    }
-    throw new Error(`Unexpected status code: ${response.status}`);
-  });
-
-  const bundle = {
-    timestamp: toNotSoISOString(new Date()),
-    locale: effectiveLocale,
-    messages
-  };
-
-  saveL10nBundleToLocalStorage(bundle);
-
-  return bundle;
-}
-
-export function getCurrentL10nBundle() {
-  return loadL10nBundleFromLocalStorage();
-}
-
-function getPreferredLanguage() {
-  return window.navigator.languages ? window.navigator.languages[0] : window.navigator.language;
-}
-
-function loadL10nBundleFromLocalStorage() {
-  let bundle: L10nBundle;
-
-  try {
-    bundle = JSON.parse(loadFromLocalStorage(L10N_BUNDLE_LS_KEY) ?? '{}');
-  } catch {
-    bundle = {};
-  }
-
-  return bundle;
-}
-
-function saveL10nBundleToLocalStorage(bundle: L10nBundle) {
-  saveInLocalStorage(L10N_BUNDLE_LS_KEY, JSON.stringify(bundle));
-}
-
-function logWarning(message: string) {
-  if (process.env.NODE_ENV !== 'production') {
-    // eslint-disable-next-line no-console
-    console.warn(message);
-  }
-}
diff --git a/server/sonar-web/src/main/js/helpers/l10nBundle.ts b/server/sonar-web/src/main/js/helpers/l10nBundle.ts
new file mode 100644 (file)
index 0000000..fc1a441
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { fetchL10nBundle } from '../api/l10n';
+import { L10nBundle, L10nBundleRequestParams } from '../types/l10nBundle';
+import { toNotSoISOString } from './dates';
+
+const DEFAULT_LOCALE = 'en';
+const DEFAULT_MESSAGES = {
+  // eslint-disable-next-line camelcase
+  default_error_message: 'The request cannot be processed. Try again later.'
+};
+
+export function getMessages() {
+  return getL10nBundleFromCache().messages ?? DEFAULT_MESSAGES;
+}
+
+export function getCurrentLocale() {
+  return getL10nBundleFromCache().locale;
+}
+
+export function getCurrentL10nBundle() {
+  return getL10nBundleFromCache();
+}
+
+export async function loadL10nBundle() {
+  const browserLocale = getPreferredLanguage();
+  const cachedBundle = getL10nBundleFromCache();
+
+  const params: L10nBundleRequestParams = {};
+
+  if (browserLocale) {
+    params.locale = browserLocale;
+
+    if (
+      cachedBundle.locale &&
+      browserLocale.startsWith(cachedBundle.locale) &&
+      cachedBundle.timestamp &&
+      cachedBundle.messages
+    ) {
+      params.ts = cachedBundle.timestamp;
+    }
+  }
+
+  const { effectiveLocale, messages } = await fetchL10nBundle(params).catch(response => {
+    if (response && response.status === 304) {
+      return {
+        effectiveLocale: cachedBundle.locale || browserLocale || DEFAULT_LOCALE,
+        messages: cachedBundle.messages ?? {}
+      };
+    }
+    throw new Error(`Unexpected status code: ${response.status}`);
+  });
+
+  const bundle = {
+    timestamp: toNotSoISOString(new Date()),
+    locale: effectiveLocale,
+    messages
+  };
+
+  persistL10nBundleInCache(bundle);
+
+  return bundle;
+}
+
+function getPreferredLanguage() {
+  return window.navigator.languages ? window.navigator.languages[0] : window.navigator.language;
+}
+
+function getL10nBundleFromCache() {
+  return ((window as unknown) as any).sonarQubeL10nBundle ?? {};
+}
+
+function persistL10nBundleInCache(bundle: L10nBundle) {
+  ((window as unknown) as any).sonarQubeL10nBundle = bundle;
+}
index cedf304914a8a92a7334467036381b863827651e..31aca41f9836e176b63e0e55497dc9e16b8bbd49 100644 (file)
@@ -23,7 +23,8 @@ import {
   QualityGateStatusConditionEnhanced
 } from '../types/quality-gates';
 import { Dict, Measure, MeasureEnhanced, Metric } from '../types/types';
-import { getCurrentLocale, translate, translateWithParameters } from './l10n';
+import { translate, translateWithParameters } from './l10n';
+import { getCurrentLocale } from './l10nBundle';
 import { isDefined } from './types';
 
 export function enhanceMeasuresWithMetrics(
diff --git a/server/sonar-web/src/main/js/helpers/mocks/editions.ts b/server/sonar-web/src/main/js/helpers/mocks/editions.ts
new file mode 100644 (file)
index 0000000..58fb0b7
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { License } from '../../types/editions';
+
+export function mockLicense(override?: Partial<License>) {
+  return {
+    contactEmail: 'contact@sonarsource.com',
+    edition: 'Developer Edition',
+    expiresAt: '2018-05-18',
+    isExpired: false,
+    isValidEdition: true,
+    isValidServerId: true,
+    isOfficialDistribution: true,
+    isSupported: false,
+    loc: 120085,
+    maxLoc: 500000,
+    plugins: ['Branches', 'PLI language'],
+    remainingLocThreshold: 490000,
+    serverId: 'AU-TpxcA-iU5OvuD2FL0',
+    type: 'production',
+    ...override
+  };
+}
index de6f688b8cc6c2df5eface52251dff3e12dfefe2..b2adcb34d6d6ba17f138b484e83ecb768547f44e 100644 (file)
@@ -27,6 +27,6 @@ export interface EnhancedWindow extends Window {
   instance: InstanceType;
   official: boolean;
 
-  registerExtension: (key: string, start: ExtensionStartMethod) => void;
+  registerExtension: (key: string, start: ExtensionStartMethod, providesCSSFile?: boolean) => void;
   setWebAnalyticsPageChangeHandler: (pageHandler: (pathname: string) => void) => void;
 }
index eb86835624c5ca2ad33c4a084174b40070564be8..82098ba44292358b8703c7fac1b7f062575b6967 100644 (file)
@@ -32,3 +32,20 @@ export interface Edition {
   key: EditionKey;
   name: string;
 }
+
+export interface License {
+  contactEmail: string;
+  edition: string;
+  expiresAt: string;
+  isExpired: boolean;
+  isOfficialDistribution: boolean;
+  isSupported: boolean;
+  isValidEdition: boolean;
+  isValidServerId: boolean;
+  loc: number;
+  maxLoc: number;
+  plugins: string[];
+  remainingLocThreshold: number;
+  serverId: string;
+  type: string;
+}
index 5ec01bd71f0e5072b041b5c9222c06a14cc6a52c..73324b6ce6605d52896411fb1970812fd99f5a05 100644 (file)
@@ -22,7 +22,7 @@ import { Store as ReduxStore } from 'redux';
 import { Location, Router } from '../components/hoc/withRouter';
 import { Store } from '../store/rootReducer';
 import { AppState } from './appstate';
-import { L10nBundle } from './l10n';
+import { L10nBundle } from './l10nBundle';
 import { Dict } from './types';
 import { CurrentUser } from './users';
 
diff --git a/server/sonar-web/src/main/js/types/l10n.ts b/server/sonar-web/src/main/js/types/l10n.ts
deleted file mode 100644 (file)
index dec56e5..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { Dict } from './types';
-
-export interface L10nBundleRequestParams {
-  locale?: string;
-  ts?: string;
-}
-
-export interface L10nBundleRequestResponse {
-  effectiveLocale: string;
-  messages: Dict<string>;
-}
-
-export interface L10nBundle {
-  timestamp?: string;
-  locale?: string;
-  messages?: Dict<string>;
-}
diff --git a/server/sonar-web/src/main/js/types/l10nBundle.ts b/server/sonar-web/src/main/js/types/l10nBundle.ts
new file mode 100644 (file)
index 0000000..0fcc3ce
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { Dict } from './types';
+
+export interface L10nBundleRequestParams {
+  locale?: string;
+  ts?: string;
+}
+
+export interface L10nBundleRequestResponse {
+  effectiveLocale: string;
+  messages: Dict<string>;
+}
+
+export interface L10nBundle {
+  timestamp?: string;
+  locale?: string;
+  messages?: Dict<string>;
+}
index bf95ca0336bfef48492d42ce8f2a784097a8117d..a9b9b5f610a9fae5bad7d4994a16c423ff505641 100644 (file)
@@ -24,7 +24,8 @@ export const enum SettingsKey {
   DaysBeforeDeletingInactiveBranchesAndPRs = 'sonar.dbcleaner.daysBeforeDeletingInactiveBranchesAndPRs',
   DefaultProjectVisibility = 'projects.default.visibility',
   ServerBaseUrl = 'sonar.core.serverBaseURL',
-  PluginRiskConsent = 'sonar.plugins.risk.consent'
+  PluginRiskConsent = 'sonar.plugins.risk.consent',
+  LicenceRemainingLocNotificationThreshold = 'sonar.license.notifications.remainingLocThreshold'
 }
 
 export enum GlobalSettingKeys {