aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorAmbroise C. <ambroise.christea@sonarsource.com>2024-07-30 14:22:27 +0200
committersonartech <sonartech@sonarsource.com>2024-07-30 20:02:34 +0000
commit06a6d3ef03c50f3733023a6eb9869d3f898ee670 (patch)
treebdea0a38eafb017c01585867570caa7b21d7512c /server
parent8597336f1aa2693413569cf790e9fbc6f5a755cf (diff)
downloadsonarqube-06a6d3ef03c50f3733023a6eb9869d3f898ee670.tar.gz
sonarqube-06a6d3ef03c50f3733023a6eb9869d3f898ee670.zip
SONAR-22515 Create email notification tab (#11363)
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx24
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx169
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/SettingsSearch.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx20
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailForm.tsx136
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailNotification.tsx46
-rw-r--r--server/sonar-web/src/main/js/apps/settings/constants.ts141
-rw-r--r--server/sonar-web/src/main/js/apps/settings/utils.ts134
-rw-r--r--server/sonar-web/src/main/js/queries/emails.ts36
10 files changed, 395 insertions, 329 deletions
diff --git a/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx b/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx
index ffdf20a0f9d..dc913bcdcee 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx
@@ -25,6 +25,7 @@ import {
ALM_INTEGRATION_CATEGORY,
ANALYSIS_SCOPE_CATEGORY,
AUTHENTICATION_CATEGORY,
+ EMAIL_NOTIFICATION_CATEGORY,
LANGUAGES_CATEGORY,
NEW_CODE_PERIOD_CATEGORY,
PULL_REQUEST_DECORATION_BINDING_CATEGORY,
@@ -34,6 +35,7 @@ import Languages from './Languages';
import NewCodeDefinition from './NewCodeDefinition';
import AlmIntegration from './almIntegration/AlmIntegration';
import Authentication from './authentication/Authentication';
+import EmailNotification from './email-notification/EmailNotification';
import PullRequestDecorationBinding from './pullRequestDecorationBinding/PRDecorationBinding';
export interface AdditionalCategoryComponentProps {
@@ -103,6 +105,14 @@ export const ADDITIONAL_CATEGORIES: AdditionalCategory[] = [
availableForProject: false,
displayTab: false,
},
+ {
+ key: EMAIL_NOTIFICATION_CATEGORY,
+ name: translate('settings.email_notification.category'),
+ renderComponent: getEmailNotificationComponent,
+ availableGlobally: true,
+ availableForProject: false,
+ displayTab: true,
+ },
];
function getLanguagesComponent(props: AdditionalCategoryComponentProps) {
@@ -128,3 +138,7 @@ function getAuthenticationComponent(props: AdditionalCategoryComponentProps) {
function getPullRequestDecorationBindingComponent(props: AdditionalCategoryComponentProps) {
return props.component && <PullRequestDecorationBinding component={props.component} />;
}
+
+function getEmailNotificationComponent(props: AdditionalCategoryComponentProps) {
+ return <EmailNotification {...props} />;
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx b/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx
index 64080ba3d7a..ad4cd62e690 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx
@@ -55,21 +55,25 @@ function CategoriesList(props: Readonly<CategoriesListProps>) {
);
const categoriesWithName = categories
- .filter((key) => !CATEGORY_OVERRIDES[key.toLowerCase()])
+ .filter((key) => CATEGORY_OVERRIDES[key.toLowerCase()] === undefined)
.map((key) => ({
key,
name: getCategoryName(key),
}))
.concat(
- ADDITIONAL_CATEGORIES.filter((c) => c.displayTab)
- .filter((c) =>
- component
- ? // Project settings
- c.availableForProject
- : // Global settings
- c.availableGlobally,
- )
- .filter((c) => props.hasFeature(Feature.BranchSupport) || !c.requiresBranchSupport),
+ ADDITIONAL_CATEGORIES.filter((c) => {
+ const availableForCurrentMenu = component
+ ? // Project settings
+ c.availableForProject
+ : // Global settings
+ c.availableGlobally;
+
+ return (
+ c.displayTab &&
+ availableForCurrentMenu &&
+ (props.hasFeature(Feature.BranchSupport) || !c.requiresBranchSupport)
+ );
+ }),
);
const sortedCategories = sortBy(categoriesWithName, (category) => category.name.toLowerCase());
diff --git a/server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx
deleted file mode 100644
index 4dfa5c97541..00000000000
--- a/server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 {
- BasicSeparator,
- ButtonPrimary,
- FlagMessage,
- FormField,
- InputField,
- InputTextArea,
- Spinner,
- SubHeading,
-} from 'design-system';
-import * as React from 'react';
-import { sendTestEmail } from '../../../api/settings';
-import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
-import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { parseError } from '../../../helpers/request';
-import { LoggedInUser } from '../../../types/users';
-
-interface Props {
- currentUser: LoggedInUser;
-}
-
-interface State {
- error?: string;
- loading: boolean;
- message: string;
- recipient: string;
- subject: string;
- success?: string;
-}
-
-export class EmailForm extends React.PureComponent<Props, State> {
- mounted = false;
-
- constructor(props: Props) {
- super(props);
- this.state = {
- recipient: this.props.currentUser.email || '',
- subject: translate('email_configuration.test.subject'),
- message: translate('email_configuration.test.message_text'),
- loading: false,
- };
- }
-
- componentDidMount() {
- this.mounted = true;
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- handleError = (response: Response) => {
- return parseError(response).then((message) => {
- if (this.mounted) {
- this.setState({ error: message, loading: false });
- }
- });
- };
-
- handleFormSubmit = (event: React.FormEvent) => {
- event.preventDefault();
- this.setState({ success: undefined, error: undefined, loading: true });
- const { recipient, subject, message } = this.state;
- sendTestEmail(recipient, subject, message).then(() => {
- if (this.mounted) {
- this.setState({ success: recipient, loading: false });
- }
- }, this.handleError);
- };
-
- onRecipientChange = (event: React.ChangeEvent<HTMLInputElement>) => {
- this.setState({ recipient: event.target.value });
- };
-
- onSubjectChange = (event: React.ChangeEvent<HTMLInputElement>) => {
- this.setState({ subject: event.target.value });
- };
-
- onMessageChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
- this.setState({ message: event.target.value });
- };
-
- render() {
- const { error, loading, message, recipient, subject, success } = this.state;
- return (
- <>
- <BasicSeparator />
- <div className="sw-p-6 sw-flex sw-gap-12">
- <div className="sw-w-abs-300">
- <SubHeading>{translate('email_configuration.test.title')}</SubHeading>
- <div className="sw-mt-1">
- <MandatoryFieldsExplanation />
- </div>
- </div>
-
- <form className="sw-flex-1" onSubmit={this.handleFormSubmit}>
- {success && (
- <FlagMessage variant="success">
- {translateWithParameters('email_configuration.test.email_was_sent_to_x', success)}
- </FlagMessage>
- )}
-
- {error !== undefined && <FlagMessage variant="error">{error}</FlagMessage>}
-
- <FormField label={translate('email_configuration.test.to_address')} required>
- <InputField
- disabled={loading}
- id="test-email-to"
- onChange={this.onRecipientChange}
- required
- size="large"
- type="email"
- value={recipient}
- />
- </FormField>
- <FormField label={translate('email_configuration.test.subject')}>
- <InputField
- disabled={loading}
- id="test-email-subject"
- onChange={this.onSubjectChange}
- size="large"
- type="text"
- value={subject}
- />
- </FormField>
- <FormField label={translate('email_configuration.test.message')} required>
- <InputTextArea
- disabled={loading}
- id="test-email-message"
- onChange={this.onMessageChange}
- required
- rows={5}
- size="large"
- value={message}
- />
- </FormField>
-
- <ButtonPrimary disabled={loading} type="submit" className="sw-mt-2">
- {translate('email_configuration.test.send')}
- </ButtonPrimary>
- <Spinner loading={loading} className="sw-ml-2" />
- </form>
- </div>
- </>
- );
- }
-}
-
-export default withCurrentUserContext(EmailForm);
diff --git a/server/sonar-web/src/main/js/apps/settings/components/SettingsSearch.tsx b/server/sonar-web/src/main/js/apps/settings/components/SettingsSearch.tsx
index 9ae187d43b7..8ae5057684a 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/SettingsSearch.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/SettingsSearch.tsx
@@ -28,8 +28,8 @@ import { Component, Dict } from '../../../types/types';
import {
ADDITIONAL_PROJECT_SETTING_DEFINITIONS,
ADDITIONAL_SETTING_DEFINITIONS,
- buildSettingLink,
-} from '../utils';
+} from '../constants';
+import { buildSettingLink } from '../utils';
import SettingsSearchRenderer from './SettingsSearchRenderer';
interface Props {
diff --git a/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx
index cbc93409ded..966415aecbc 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx
@@ -25,9 +25,9 @@ import { Location } from '~sonar-aligned/types/router';
import { sanitizeStringRestricted } from '../../../helpers/sanitize';
import { SettingDefinitionAndValue } from '../../../types/settings';
import { Component } from '../../../types/types';
+import { SUB_CATEGORY_EXCLUSIONS } from '../constants';
import { getSubCategoryDescription, getSubCategoryName } from '../utils';
import DefinitionsList from './DefinitionsList';
-import EmailForm from './EmailForm';
export interface SubCategoryDefinitionsListProps {
category: string;
@@ -42,7 +42,7 @@ export interface SubCategoryDefinitionsListProps {
class SubCategoryDefinitionsList extends React.PureComponent<SubCategoryDefinitionsListProps> {
componentDidUpdate(prevProps: SubCategoryDefinitionsListProps) {
const { hash } = this.props.location;
- if (hash && prevProps.location.hash !== hash) {
+ if (hash.length > 0 && prevProps.location.hash !== hash) {
const query = `[data-scroll-key=${hash.substring(1).replace(/[.#/]/g, '\\$&')}]`;
const element = document.querySelector<HTMLHeadingElement | HTMLLIElement>(query);
this.scrollToSubCategoryOrDefinition(element);
@@ -52,22 +52,15 @@ class SubCategoryDefinitionsList extends React.PureComponent<SubCategoryDefiniti
scrollToSubCategoryOrDefinition = (element: HTMLHeadingElement | HTMLLIElement | null) => {
if (element) {
const { hash } = this.props.location;
- if (hash && hash.substring(1) === element.getAttribute('data-scroll-key')) {
+ if (hash.length > 0 && hash.substring(1) === element.getAttribute('data-scroll-key')) {
element.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
}
}
};
- renderEmailForm = (subCategoryKey: string) => {
- const isEmailSettings = this.props.category === 'general' && subCategoryKey === 'email';
- if (!isEmailSettings) {
- return null;
- }
- return <EmailForm />;
- };
-
render() {
const {
+ category,
displaySubCategoryTitle = true,
settings,
subCategory,
@@ -85,7 +78,8 @@ class SubCategoryDefinitionsList extends React.PureComponent<SubCategoryDefiniti
);
const filteredSubCategories = subCategory
? sortedSubCategories.filter((c) => c.key === subCategory)
- : sortedSubCategories;
+ : sortedSubCategories.filter((c) => !SUB_CATEGORY_EXCLUSIONS[category]?.includes(c.key));
+
return (
<ul>
{filteredSubCategories.map((subCategory, index) => (
@@ -114,8 +108,6 @@ class SubCategoryDefinitionsList extends React.PureComponent<SubCategoryDefiniti
scrollToDefinition={this.scrollToSubCategoryOrDefinition}
settings={bySubCategory[subCategory.key]}
/>
- {this.renderEmailForm(subCategory.key)}
-
{
// Add a separator to all but the last element
index !== filteredSubCategories.length - 1 && <BasicSeparator />
diff --git a/server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailForm.tsx
new file mode 100644
index 00000000000..4f5c24df21b
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailForm.tsx
@@ -0,0 +1,136 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 { Spinner } from '@sonarsource/echoes-react';
+import {
+ BasicSeparator,
+ ButtonPrimary,
+ FlagMessage,
+ FormField,
+ InputField,
+ InputTextArea,
+ SubHeading,
+} from 'design-system';
+import React, { ChangeEvent, FormEvent, useState } from 'react';
+import { useIntl } from 'react-intl';
+import { useCurrentLoginUser } from '../../../../app/components/current-user/CurrentUserContext';
+import MandatoryFieldsExplanation from '../../../../components/ui/MandatoryFieldsExplanation';
+import { translate, translateWithParameters } from '../../../../helpers/l10n';
+import { parseError } from '../../../../helpers/request';
+import { useSendTestEmailMutation } from '../../../../queries/emails';
+
+export default function EmailForm() {
+ const currentUser = useCurrentLoginUser();
+ const { formatMessage } = useIntl();
+ const { isPending, isSuccess, mutate: sendTestEmail } = useSendTestEmailMutation();
+
+ const [error, setError] = useState<string | undefined>();
+ const [message, setMessage] = useState(
+ formatMessage({ id: 'email_configuration.test.message_text' }),
+ );
+ const [recipient, setRecipient] = useState(currentUser.email ?? '');
+ const [subject, setSubject] = useState(formatMessage({ id: 'email_configuration.test.subject' }));
+
+ const handleFormSubmit = (event: FormEvent) => {
+ event.preventDefault();
+ sendTestEmail(
+ { message, recipient, subject },
+ {
+ onError: async (error) => {
+ const errorMessage = await parseError(error);
+ setError(errorMessage);
+ },
+ },
+ );
+ };
+
+ const onMessageChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
+ setMessage(event.target.value);
+ };
+
+ const onRecipientChange = (event: ChangeEvent<HTMLInputElement>) => {
+ setRecipient(event.target.value);
+ };
+
+ const onSubjectChange = (event: ChangeEvent<HTMLInputElement>) => {
+ setSubject(event.target.value);
+ };
+
+ return (
+ <>
+ <BasicSeparator />
+ <div className="sw-p-6 sw-flex sw-gap-12">
+ <div className="sw-w-abs-300">
+ <SubHeading>{translate('email_configuration.test.title')}</SubHeading>
+ <div className="sw-mt-1">
+ <MandatoryFieldsExplanation />
+ </div>
+ </div>
+
+ <form className="sw-flex-1" onSubmit={handleFormSubmit}>
+ {isSuccess && (
+ <FlagMessage variant="success">
+ {translateWithParameters('email_configuration.test.email_was_sent_to_x', recipient)}
+ </FlagMessage>
+ )}
+
+ {error !== undefined && <FlagMessage variant="error">{error}</FlagMessage>}
+
+ <FormField label={translate('email_configuration.test.to_address')} required>
+ <InputField
+ disabled={isPending}
+ id="test-email-to"
+ onChange={onRecipientChange}
+ required
+ size="large"
+ type="email"
+ value={recipient}
+ />
+ </FormField>
+ <FormField label={translate('email_configuration.test.subject')}>
+ <InputField
+ disabled={isPending}
+ id="test-email-subject"
+ onChange={onSubjectChange}
+ size="large"
+ type="text"
+ value={subject}
+ />
+ </FormField>
+ <FormField label={translate('email_configuration.test.message')} required>
+ <InputTextArea
+ disabled={isPending}
+ id="test-email-message"
+ onChange={onMessageChange}
+ required
+ rows={5}
+ size="large"
+ value={message}
+ />
+ </FormField>
+
+ <ButtonPrimary disabled={isPending} type="submit" className="sw-mt-2">
+ {translate('email_configuration.test.send')}
+ </ButtonPrimary>
+ <Spinner isLoading={isPending} className="sw-ml-2" />
+ </form>
+ </div>
+ </>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailNotification.tsx b/server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailNotification.tsx
new file mode 100644
index 00000000000..b4bd95e965d
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailNotification.tsx
@@ -0,0 +1,46 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 { SubTitle } from 'design-system';
+import React from 'react';
+import { FormattedMessage } from 'react-intl';
+import { AdditionalCategoryComponentProps } from '../AdditionalCategories';
+import CategoryDefinitionsList from '../CategoryDefinitionsList';
+import EmailForm from './EmailForm';
+
+export default function EmailNotification(props: Readonly<AdditionalCategoryComponentProps>) {
+ const { component, definitions } = props;
+
+ return (
+ <div>
+ <SubTitle as="h3">
+ <FormattedMessage id="settings.email_notification.header" />
+ </SubTitle>
+ <CategoryDefinitionsList
+ category="general"
+ component={component}
+ definitions={definitions}
+ displaySubCategoryTitle={false}
+ noPadding
+ subCategory="email"
+ />
+ <EmailForm />
+ </div>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/constants.ts b/server/sonar-web/src/main/js/apps/settings/constants.ts
index 3b413065239..4ab46992bbf 100644
--- a/server/sonar-web/src/main/js/apps/settings/constants.ts
+++ b/server/sonar-web/src/main/js/apps/settings/constants.ts
@@ -17,6 +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 { AlmKeys } from '../../types/alm-settings';
+import { ExtendedSettingDefinition } from '../../types/settings';
import { Dict } from '../../types/types';
export const ALM_INTEGRATION_CATEGORY = 'almintegration';
@@ -25,6 +27,7 @@ export const ANALYSIS_SCOPE_CATEGORY = 'exclusions';
export const LANGUAGES_CATEGORY = 'languages';
export const NEW_CODE_PERIOD_CATEGORY = 'new_code_period';
export const PULL_REQUEST_DECORATION_BINDING_CATEGORY = 'pull_request_decoration_binding';
+export const EMAIL_NOTIFICATION_CATEGORY = 'email_notification';
export const CATEGORY_OVERRIDES: Dict<string> = {
abap: LANGUAGES_CATEGORY,
@@ -65,6 +68,144 @@ export const CATEGORY_OVERRIDES: Dict<string> = {
yaml: LANGUAGES_CATEGORY,
};
+export const SUB_CATEGORY_EXCLUSIONS: Record<string, string[]> = {
+ general: ['email'],
+};
+
// As per Bitbucket Cloud's documentation, Workspace ID's can only contain lowercase letters,
// numbers, dashes, and underscores.
export const BITBUCKET_CLOUD_WORKSPACE_ID_FORMAT = /^[a-z0-9\-_]+$/;
+
+export const ADDITIONAL_PROJECT_SETTING_DEFINITIONS: ExtendedSettingDefinition[] = [
+ {
+ name: 'DevOps Platform Integration',
+ description: `
+ Display your Quality Gate status directly in your DevOps Platform.
+ Each DevOps Platform instance must be configured globally first, and given a unique name. Pick the instance your project is hosted on.
+ `,
+ category: 'pull_request_decoration_binding',
+ key: ``,
+ fields: [],
+ options: [],
+ subCategory: '',
+ },
+];
+
+export const ADDITIONAL_SETTING_DEFINITIONS: ExtendedSettingDefinition[] = [
+ {
+ name: 'Default New Code behavior',
+ description: `
+ The New Code definition is used to compare measures and track new issues.
+ This setting is the default for all projects. A specific New Code definition can be configured at project level.
+ `,
+ category: 'new_code_period',
+ key: `sonar.new_code_period`,
+ fields: [],
+ options: [],
+ subCategory: '',
+ },
+ {
+ name: 'Azure DevOps integration',
+ description: `azure devops integration configuration
+ Configuration name
+ Give your configuration a clear and succinct name.
+ This name will be used at project level to identify the correct configured Azure instance for a project.
+ Azure DevOps URL
+ For Azure DevOps Server, provide the full collection URL:
+ https://ado.your-company.com/your_collection
+
+ For Azure DevOps Services, provide the full organization URL:
+ https://dev.azure.com/your_organization
+ Personal Access Token
+ SonarQube needs a Personal Access Token to report the Quality Gate status on Pull Requests in Azure DevOps.
+ To create this token, we recommend using a dedicated Azure DevOps account with administration permissions.
+ The token itself needs Code > Read & Write permission.
+ `,
+ category: 'almintegration',
+ key: `sonar.almintegration.${AlmKeys.Azure}`,
+ fields: [],
+ options: [],
+ subCategory: '',
+ },
+ {
+ name: 'Bitbucket integration',
+ description: `bitbucket server cloud integration configuration
+ Configuration name
+ Give your configuration a clear and succinct name.
+ This name will be used at project level to identify the correct configured Bitbucket instance for a project.
+ Bitbucket Server URL
+ Example: https://bitbucket-server.your-company.com
+ Personal Access Token
+ SonarQube needs a Personal Access Token to report the Quality Gate status on Pull Requests in Bitbucket Server.
+ To create this token, we recommend using a dedicated Bitbucket Server account with administration permissions.
+ The token itself needs Read permission.
+ Workspace ID
+ The workspace ID is part of your bitbucket cloud URL https://bitbucket.org/{workspace}/{repository}
+ SonarQube needs you to create an OAuth consumer in your Bitbucket Cloud workspace settings
+ to report the Quality Gate status on Pull Requests.
+ It needs to be a private consumer with Pull Requests: Read permission.
+ An OAuth callback URL is required by Bitbucket Cloud but not used by SonarQube so any URL works.
+ OAuth Key
+ Bitbucket automatically creates an OAuth key when you create your OAuth consumer.
+ You can find it in your Bitbucket Cloud workspace settings under OAuth consumers.
+ OAuth Secret
+ Bitbucket automatically creates an OAuth secret when you create your OAuth consumer.
+ You can find it in your Bitbucket Cloud workspace settings under OAuth consumers.
+ `,
+ category: 'almintegration',
+ key: `sonar.almintegration.${AlmKeys.BitbucketServer}`,
+ fields: [],
+ options: [],
+ subCategory: '',
+ },
+ {
+ name: 'GitHub integration',
+ description: `github integration configuration
+ Configuration name
+ Give your configuration a clear and succinct name.
+ This name will be used at project level to identify the correct configured GitHub App for a project.
+ GitHub API URL
+ Example for Github Enterprise:
+ https://github.company.com/api/v3
+ If using GitHub.com:
+ https://api.github.com/
+ You need to install a GitHub App with specific settings and permissions to enable
+ Pull Request Decoration on your Organization or Repository.
+ GitHub App ID
+ The App ID is found on your GitHub App's page on GitHub at Settings > Developer Settings > GitHub Apps
+ Client ID
+ The Client ID is found on your GitHub App's page.
+ Client Secret
+ The Client secret is found on your GitHub App's page.
+ Private Key
+ Your GitHub App's private key. You can generate a .pem file from your GitHub App's page under Private keys.
+ Copy and paste the whole contents of the file here.
+ `,
+ category: 'almintegration',
+ key: `sonar.almintegration.${AlmKeys.GitHub}`,
+ fields: [],
+ options: [],
+ subCategory: '',
+ },
+ {
+ name: 'Gitlab integration',
+ description: `gitlab integration configuration
+ Configuration name
+ Give your configuration a clear and succinct name.
+ This name will be used at project level to identify the correct configured GitLab instance for a project.
+ GitLab API URL
+ Provide the GitLab API URL. For example:
+ https://gitlab.com/api/v4
+ Personal Access Token
+ SonarQube needs a Personal Access Token to report the Quality Gate status on Merge Requests in GitLab.
+ To create this token,
+ we recommend using a dedicated GitLab account with Reporter permission to all target projects.
+ The token itself needs the api scope.
+ `,
+ category: 'almintegration',
+ key: `sonar.almintegration.${AlmKeys.GitLab}`,
+ fields: [],
+ options: [],
+ subCategory: '',
+ },
+];
diff --git a/server/sonar-web/src/main/js/apps/settings/utils.ts b/server/sonar-web/src/main/js/apps/settings/utils.ts
index 81d24b6c2a1..6f76b731c6a 100644
--- a/server/sonar-web/src/main/js/apps/settings/utils.ts
+++ b/server/sonar-web/src/main/js/apps/settings/utils.ts
@@ -258,137 +258,3 @@ export function buildSettingLink(
hash: `#${escape(key)}`,
};
}
-
-export const ADDITIONAL_PROJECT_SETTING_DEFINITIONS: ExtendedSettingDefinition[] = [
- {
- name: 'DevOps Platform Integration',
- description: `
- Display your Quality Gate status directly in your DevOps Platform.
- Each DevOps Platform instance must be configured globally first, and given a unique name. Pick the instance your project is hosted on.
- `,
- category: 'pull_request_decoration_binding',
- key: ``,
- fields: [],
- options: [],
- subCategory: '',
- },
-];
-
-export const ADDITIONAL_SETTING_DEFINITIONS: ExtendedSettingDefinition[] = [
- {
- name: 'Default New Code behavior',
- description: `
- The New Code definition is used to compare measures and track new issues.
- This setting is the default for all projects. A specific New Code definition can be configured at project level.
- `,
- category: 'new_code_period',
- key: `sonar.new_code_period`,
- fields: [],
- options: [],
- subCategory: '',
- },
- {
- name: 'Azure DevOps integration',
- description: `azure devops integration configuration
- Configuration name
- Give your configuration a clear and succinct name.
- This name will be used at project level to identify the correct configured Azure instance for a project.
- Azure DevOps URL
- For Azure DevOps Server, provide the full collection URL:
- https://ado.your-company.com/your_collection
-
- For Azure DevOps Services, provide the full organization URL:
- https://dev.azure.com/your_organization
- Personal Access Token
- SonarQube needs a Personal Access Token to report the Quality Gate status on Pull Requests in Azure DevOps.
- To create this token, we recommend using a dedicated Azure DevOps account with administration permissions.
- The token itself needs Code > Read & Write permission.
- `,
- category: 'almintegration',
- key: `sonar.almintegration.${AlmKeys.Azure}`,
- fields: [],
- options: [],
- subCategory: '',
- },
- {
- name: 'Bitbucket integration',
- description: `bitbucket server cloud integration configuration
- Configuration name
- Give your configuration a clear and succinct name.
- This name will be used at project level to identify the correct configured Bitbucket instance for a project.
- Bitbucket Server URL
- Example: https://bitbucket-server.your-company.com
- Personal Access Token
- SonarQube needs a Personal Access Token to report the Quality Gate status on Pull Requests in Bitbucket Server.
- To create this token, we recommend using a dedicated Bitbucket Server account with administration permissions.
- The token itself needs Read permission.
- Workspace ID
- The workspace ID is part of your bitbucket cloud URL https://bitbucket.org/{workspace}/{repository}
- SonarQube needs you to create an OAuth consumer in your Bitbucket Cloud workspace settings
- to report the Quality Gate status on Pull Requests.
- It needs to be a private consumer with Pull Requests: Read permission.
- An OAuth callback URL is required by Bitbucket Cloud but not used by SonarQube so any URL works.
- OAuth Key
- Bitbucket automatically creates an OAuth key when you create your OAuth consumer.
- You can find it in your Bitbucket Cloud workspace settings under OAuth consumers.
- OAuth Secret
- Bitbucket automatically creates an OAuth secret when you create your OAuth consumer.
- You can find it in your Bitbucket Cloud workspace settings under OAuth consumers.
- `,
- category: 'almintegration',
- key: `sonar.almintegration.${AlmKeys.BitbucketServer}`,
- fields: [],
- options: [],
- subCategory: '',
- },
- {
- name: 'GitHub integration',
- description: `github integration configuration
- Configuration name
- Give your configuration a clear and succinct name.
- This name will be used at project level to identify the correct configured GitHub App for a project.
- GitHub API URL
- Example for Github Enterprise:
- https://github.company.com/api/v3
- If using GitHub.com:
- https://api.github.com/
- You need to install a GitHub App with specific settings and permissions to enable
- Pull Request Decoration on your Organization or Repository.
- GitHub App ID
- The App ID is found on your GitHub App's page on GitHub at Settings > Developer Settings > GitHub Apps
- Client ID
- The Client ID is found on your GitHub App's page.
- Client Secret
- The Client secret is found on your GitHub App's page.
- Private Key
- Your GitHub App's private key. You can generate a .pem file from your GitHub App's page under Private keys.
- Copy and paste the whole contents of the file here.
- `,
- category: 'almintegration',
- key: `sonar.almintegration.${AlmKeys.GitHub}`,
- fields: [],
- options: [],
- subCategory: '',
- },
- {
- name: 'Gitlab integration',
- description: `gitlab integration configuration
- Configuration name
- Give your configuration a clear and succinct name.
- This name will be used at project level to identify the correct configured GitLab instance for a project.
- GitLab API URL
- Provide the GitLab API URL. For example:
- https://gitlab.com/api/v4
- Personal Access Token
- SonarQube needs a Personal Access Token to report the Quality Gate status on Merge Requests in GitLab.
- To create this token,
- we recommend using a dedicated GitLab account with Reporter permission to all target projects.
- The token itself needs the api scope.
- `,
- category: 'almintegration',
- key: `sonar.almintegration.${AlmKeys.GitLab}`,
- fields: [],
- options: [],
- subCategory: '',
- },
-];
diff --git a/server/sonar-web/src/main/js/queries/emails.ts b/server/sonar-web/src/main/js/queries/emails.ts
new file mode 100644
index 00000000000..091154eb505
--- /dev/null
+++ b/server/sonar-web/src/main/js/queries/emails.ts
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 { useMutation } from '@tanstack/react-query';
+import { sendTestEmail } from '../api/settings';
+
+export function useSendTestEmailMutation() {
+ return useMutation<
+ void,
+ Response,
+ {
+ message: string;
+ recipient: string;
+ subject: string;
+ },
+ unknown
+ >({
+ mutationFn: ({ message, recipient, subject }) => sendTestEmail(message, recipient, subject),
+ });
+}