aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/App.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/DeleteWebhookForm.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/LatestDeliveryForm.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/UpdateWebhookSecretField.tsx99
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/WebhookActions.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/WebhookItem.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/WebhookItemLatestDelivery.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/WebhooksList.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/App-test.tsx67
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/CreateWebhookForm-it.tsx145
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/CreateWebhookForm-test.tsx43
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/DeliveriesForm-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/LatestDeliveryForm-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookActions-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookItem-test.tsx21
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookItemLatestDelivery-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhooksList-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/App-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/CreateWebhookForm-test.tsx.snap61
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhookItem-test.tsx.snap46
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhooksList-test.tsx.snap8
23 files changed, 428 insertions, 167 deletions
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx
index 6e88a09c912..3c02424500c 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx
@@ -24,7 +24,7 @@ import withComponentContext from '../../../app/components/componentContext/withC
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
import { translate } from '../../../helpers/l10n';
import { Component } from '../../../types/types';
-import { Webhook } from '../../../types/webhook';
+import { WebhookResponse } from '../../../types/webhook';
import PageActions from './PageActions';
import PageHeader from './PageHeader';
import WebhooksList from './WebhooksList';
@@ -36,7 +36,7 @@ interface Props {
interface State {
loading: boolean;
- webhooks: Webhook[];
+ webhooks: WebhookResponse[];
}
export class App extends React.PureComponent<Props, State> {
@@ -99,19 +99,24 @@ export class App extends React.PureComponent<Props, State> {
};
handleUpdate = (data: { webhook: string; name: string; secret?: string; url: string }) => {
- const udpateData = {
+ const updateData = {
webhook: data.webhook,
name: data.name,
url: data.url,
- ...(data.secret && { secret: data.secret }),
+ secret: data.secret,
};
- return updateWebhook(udpateData).then(() => {
+ return updateWebhook(updateData).then(() => {
if (this.mounted) {
this.setState(({ webhooks }) => ({
webhooks: webhooks.map((webhook) =>
webhook.key === data.webhook
- ? { ...webhook, name: data.name, secret: data.secret, url: data.url }
+ ? {
+ ...webhook,
+ name: data.name,
+ hasSecret: data.secret === undefined ? webhook.hasSecret : Boolean(data.secret),
+ url: data.url,
+ }
: webhook
),
}));
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx
index 5d1833ac179..602fdea8986 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx
@@ -24,18 +24,13 @@ import ValidationModal from '../../../components/controls/ValidationModal';
import MandatoryFieldMarker from '../../../components/ui/MandatoryFieldMarker';
import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
import { translate } from '../../../helpers/l10n';
-import { Webhook } from '../../../types/webhook';
+import { WebhookBasePayload, WebhookResponse } from '../../../types/webhook';
+import UpdateWebhookSecretField from './UpdateWebhookSecretField';
interface Props {
onClose: () => void;
- onDone: (data: Values) => Promise<void>;
- webhook?: Webhook;
-}
-
-interface Values {
- name: string;
- secret: string;
- url: string;
+ onDone: (data: WebhookBasePayload) => Promise<void>;
+ webhook?: WebhookResponse;
}
export default class CreateWebhookForm extends React.PureComponent<Props> {
@@ -45,7 +40,7 @@ export default class CreateWebhookForm extends React.PureComponent<Props> {
this.props.onClose();
};
- handleValidate = (data: Values) => {
+ handleValidate = (data: WebhookBasePayload) => {
const { name, secret, url } = data;
const errors: { name?: string; secret?: string; url?: string } = {};
if (!name.trim()) {
@@ -74,9 +69,9 @@ export default class CreateWebhookForm extends React.PureComponent<Props> {
confirmButtonText={confirmButtonText}
header={modalHeader}
initialValues={{
- name: (webhook && webhook.name) || '',
- secret: (webhook && webhook.secret) || '',
- url: (webhook && webhook.url) || '',
+ name: webhook?.name ?? '',
+ url: webhook?.url ?? '',
+ secret: isUpdate ? undefined : '',
}}
onClose={this.props.onClose}
onSubmit={this.props.onDone}
@@ -125,12 +120,15 @@ export default class CreateWebhookForm extends React.PureComponent<Props> {
type="text"
value={values.url}
/>
- <InputValidationField
- description={translate('webhooks.secret.description')}
+ <UpdateWebhookSecretField
+ description={`${translate('webhooks.secret.description')}${
+ isUpdate ? ` ${translate('webhooks.secret.description.update')}` : ''
+ }`}
dirty={dirty}
disabled={isSubmitting}
error={errors.secret}
id="webhook-secret"
+ isUpdateForm={isUpdate}
label={<label htmlFor="webhook-secret">{translate('webhooks.secret')}</label>}
name="secret"
onBlur={handleBlur}
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/DeleteWebhookForm.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/DeleteWebhookForm.tsx
index 6ec97f173ce..238ec10c8b9 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/DeleteWebhookForm.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/DeleteWebhookForm.tsx
@@ -18,16 +18,16 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
import SimpleModal from '../../../components/controls/SimpleModal';
+import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { Webhook } from '../../../types/webhook';
+import { WebhookResponse } from '../../../types/webhook';
interface Props {
onClose: () => void;
onSubmit: () => Promise<void>;
- webhook: Webhook;
+ webhook: WebhookResponse;
}
export default function DeleteWebhookForm({ onClose, onSubmit, webhook }: Props) {
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx
index 9dfad0d5b71..85e70128724 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx
@@ -19,18 +19,18 @@
*/
import * as React from 'react';
import { searchDeliveries } from '../../../api/webhooks';
-import { ResetButtonLink } from '../../../components/controls/buttons';
import ListFooter from '../../../components/controls/ListFooter';
import Modal from '../../../components/controls/Modal';
+import { ResetButtonLink } from '../../../components/controls/buttons';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Paging } from '../../../types/types';
-import { Webhook, WebhookDelivery } from '../../../types/webhook';
+import { WebhookDelivery, WebhookResponse } from '../../../types/webhook';
import DeliveryAccordion from './DeliveryAccordion';
interface Props {
onClose: () => void;
- webhook: Webhook;
+ webhook: WebhookResponse;
}
interface State {
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/LatestDeliveryForm.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/LatestDeliveryForm.tsx
index d4dba71fc42..ceedc15589c 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/LatestDeliveryForm.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/LatestDeliveryForm.tsx
@@ -19,16 +19,16 @@
*/
import * as React from 'react';
import { getDelivery } from '../../../api/webhooks';
-import { ResetButtonLink } from '../../../components/controls/buttons';
import Modal from '../../../components/controls/Modal';
+import { ResetButtonLink } from '../../../components/controls/buttons';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { Webhook, WebhookDelivery } from '../../../types/webhook';
+import { WebhookDelivery, WebhookResponse } from '../../../types/webhook';
import DeliveryItem from './DeliveryItem';
interface Props {
delivery: WebhookDelivery;
onClose: () => void;
- webhook: Webhook;
+ webhook: WebhookResponse;
}
interface State {
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/UpdateWebhookSecretField.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/UpdateWebhookSecretField.tsx
new file mode 100644
index 00000000000..6c01c7bf683
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/UpdateWebhookSecretField.tsx
@@ -0,0 +1,99 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { useField } from 'formik';
+import * as React from 'react';
+import InputValidationField from '../../../components/controls/InputValidationField';
+import ModalValidationField from '../../../components/controls/ModalValidationField';
+import { ButtonLink } from '../../../components/controls/buttons';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ description?: string;
+ dirty: boolean;
+ disabled: boolean;
+ error: string | undefined;
+ id?: string;
+ isUpdateForm: boolean;
+ label?: React.ReactNode;
+ name: string;
+ onBlur: (event: React.FocusEvent<HTMLInputElement>) => void;
+ onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
+ touched: boolean | undefined;
+ type?: string;
+ value?: string;
+}
+
+export default function UpdateWebhookSecretField({
+ isUpdateForm,
+ description,
+ dirty,
+ disabled,
+ error,
+ id,
+ label,
+ name,
+ onBlur,
+ onChange,
+ touched,
+ type,
+ value,
+}: Props) {
+ const [isSecretInputDisplayed, setIsSecretInputDisplayed] = React.useState(false);
+ const [, , { setValue: setSecretValue }] = useField('secret');
+
+ const showSecretInput = () => {
+ setSecretValue('');
+ setIsSecretInputDisplayed(true);
+ };
+
+ return !isUpdateForm || isSecretInputDisplayed ? (
+ <InputValidationField
+ description={description}
+ dirty={dirty}
+ disabled={disabled}
+ error={error}
+ id={id}
+ label={label}
+ name={name}
+ onBlur={onBlur}
+ onChange={onChange}
+ touched={touched}
+ type={type}
+ value={value as string}
+ />
+ ) : (
+ <ModalValidationField
+ description={description}
+ dirty={false}
+ error={undefined}
+ label={label}
+ touched={true}
+ >
+ {() => (
+ <div className="sw-mb-5 sw-leading-6 sw-flex sw-items-center">
+ <span className="sw-mr-1/2">{translate('webhooks.secret.field_mask.description')}</span>
+ <ButtonLink onClick={showSecretInput}>
+ {translate('webhooks.secret.field_mask.link')}
+ </ButtonLink>
+ </div>
+ )}
+ </ModalValidationField>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/WebhookActions.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/WebhookActions.tsx
index 4a97aa0b15c..75a89cefd4f 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/WebhookActions.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/WebhookActions.tsx
@@ -23,15 +23,15 @@ import ActionsDropdown, {
ActionsDropdownItem,
} from '../../../components/controls/ActionsDropdown';
import { translate } from '../../../helpers/l10n';
-import { Webhook } from '../../../types/webhook';
+import { WebhookResponse, WebhookUpdatePayload } from '../../../types/webhook';
import CreateWebhookForm from './CreateWebhookForm';
import DeleteWebhookForm from './DeleteWebhookForm';
import DeliveriesForm from './DeliveriesForm';
interface Props {
onDelete: (webhook: string) => Promise<void>;
- onUpdate: (data: { webhook: string; name: string; url: string }) => Promise<void>;
- webhook: Webhook;
+ onUpdate: (data: WebhookUpdatePayload) => Promise<void>;
+ webhook: WebhookResponse;
}
interface State {
@@ -74,7 +74,7 @@ export default class WebhookActions extends React.PureComponent<Props, State> {
this.setState({ deliveries: false });
};
- handleUpdate = (data: { name: string; url: string }) => {
+ handleUpdate = (data: { name: string; secret?: string; url: string }) => {
return this.props.onUpdate({ ...data, webhook: this.props.webhook.key });
};
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItem.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItem.tsx
index 51ef3dc2236..1e69c832adc 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItem.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItem.tsx
@@ -19,14 +19,14 @@
*/
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
-import { Webhook } from '../../../types/webhook';
+import { WebhookResponse, WebhookUpdatePayload } from '../../../types/webhook';
import WebhookActions from './WebhookActions';
import WebhookItemLatestDelivery from './WebhookItemLatestDelivery';
interface Props {
onDelete: (webhook: string) => Promise<void>;
- onUpdate: (data: { webhook: string; name: string; url: string }) => Promise<void>;
- webhook: Webhook;
+ onUpdate: (data: WebhookUpdatePayload) => Promise<void>;
+ webhook: WebhookResponse;
}
export default function WebhookItem({ onDelete, onUpdate, webhook }: Props) {
@@ -34,11 +34,11 @@ export default function WebhookItem({ onDelete, onUpdate, webhook }: Props) {
<tr>
<td>{webhook.name}</td>
<td>{webhook.url}</td>
- <td>{webhook.secret ? translate('yes') : translate('no')}</td>
+ <td>{webhook.hasSecret ? translate('yes') : translate('no')}</td>
<td>
<WebhookItemLatestDelivery webhook={webhook} />
</td>
- <td className="thin nowrap text-right">
+ <td className="sw-text-right">
<WebhookActions onDelete={onDelete} onUpdate={onUpdate} webhook={webhook} />
</td>
</tr>
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItemLatestDelivery.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItemLatestDelivery.tsx
index d932cf8633a..c896aa43738 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItemLatestDelivery.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItemLatestDelivery.tsx
@@ -24,11 +24,11 @@ import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon';
import BulletListIcon from '../../../components/icons/BulletListIcon';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import { translate } from '../../../helpers/l10n';
-import { Webhook } from '../../../types/webhook';
+import { WebhookResponse } from '../../../types/webhook';
import LatestDeliveryForm from './LatestDeliveryForm';
interface Props {
- webhook: Webhook;
+ webhook: WebhookResponse;
}
interface State {
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/WebhooksList.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/WebhooksList.tsx
index 08135a70143..b60aa9008c4 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/WebhooksList.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/WebhooksList.tsx
@@ -20,13 +20,13 @@
import { sortBy } from 'lodash';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
-import { Webhook } from '../../../types/webhook';
+import { WebhookResponse, WebhookUpdatePayload } from '../../../types/webhook';
import WebhookItem from './WebhookItem';
interface Props {
onDelete: (webhook: string) => Promise<void>;
- onUpdate: (data: { webhook: string; name: string; url: string }) => Promise<void>;
- webhooks: Webhook[];
+ onUpdate: (data: WebhookUpdatePayload) => Promise<void>;
+ webhooks: WebhookResponse[];
}
export default class WebhooksList extends React.PureComponent<Props> {
@@ -37,7 +37,7 @@ export default class WebhooksList extends React.PureComponent<Props> {
<th>{translate('webhooks.url')}</th>
<th>{translate('webhooks.secret_header')}</th>
<th>{translate('webhooks.last_execution')}</th>
- <th />
+ <th className="sw-text-right">{translate('actions')}</th>
</tr>
</thead>
);
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/App-test.tsx
index 69fd13fd6d4..1aa92f4b207 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/App-test.tsx
@@ -30,14 +30,14 @@ import { App } from '../App';
jest.mock('../../../../api/webhooks', () => ({
createWebhook: jest.fn(() =>
- Promise.resolve({ webhook: { key: '3', name: 'baz', url: 'http://baz' } })
+ Promise.resolve({ webhook: { key: '3', name: 'baz', url: 'http://baz', hasSecret: false } })
),
deleteWebhook: jest.fn(() => Promise.resolve()),
searchWebhooks: jest.fn(() =>
Promise.resolve({
webhooks: [
- { key: '1', name: 'foo', url: 'http://foo' },
- { key: '2', name: 'bar', url: 'http://bar' },
+ { key: '1', name: 'foo', url: 'http://foo', hasSecret: false },
+ { key: '2', name: 'bar', url: 'http://bar', hasSecret: false },
],
})
),
@@ -98,9 +98,9 @@ it('should correctly handle webhook creation', async () => {
await new Promise(setImmediate);
wrapper.update();
expect(wrapper.state('webhooks')).toEqual([
- { key: '1', name: 'foo', url: 'http://foo' },
- { key: '2', name: 'bar', url: 'http://bar' },
- { key: '3', name: 'baz', url: 'http://baz' },
+ { key: '1', name: 'foo', url: 'http://foo', hasSecret: false },
+ { key: '2', name: 'bar', url: 'http://bar', hasSecret: false },
+ { key: '3', name: 'baz', url: 'http://baz', hasSecret: false },
]);
});
@@ -111,11 +111,13 @@ it('should correctly handle webhook deletion', async () => {
await new Promise(setImmediate);
wrapper.update();
- expect(wrapper.state('webhooks')).toEqual([{ key: '1', name: 'foo', url: 'http://foo' }]);
+ expect(wrapper.state('webhooks')).toEqual([
+ { key: '1', name: 'foo', url: 'http://foo', hasSecret: false },
+ ]);
});
it('should correctly handle webhook update', async () => {
- const newValues = { webhook: '1', name: 'Cfoo', url: 'http://cfoo' };
+ const newValues = { webhook: '1', name: 'Cfoo', url: 'http://cfoo', secret: undefined };
const wrapper = shallow(<App />);
(wrapper.instance() as App).handleUpdate(newValues);
expect(updateWebhook).toHaveBeenLastCalledWith(newValues);
@@ -123,7 +125,52 @@ it('should correctly handle webhook update', async () => {
await new Promise(setImmediate);
wrapper.update();
expect(wrapper.state('webhooks')).toEqual([
- { key: '1', name: 'Cfoo', url: 'http://cfoo' },
- { key: '2', name: 'bar', url: 'http://bar' },
+ { key: '1', name: 'Cfoo', url: 'http://cfoo', hasSecret: false },
+ { key: '2', name: 'bar', url: 'http://bar', hasSecret: false },
+ ]);
+});
+
+it('should correctly handle webhook secret update', async () => {
+ const newValuesWithSecret = { webhook: '2', name: 'bar', url: 'http://bar', secret: 'secret' };
+ const newValuesWithoutSecret = {
+ webhook: '2',
+ name: 'bar',
+ url: 'http://bar',
+ secret: undefined,
+ };
+ const newValuesWithEmptySecret = { webhook: '2', name: 'bar', url: 'http://bar', secret: '' };
+ const wrapper = shallow(<App />);
+
+ // With secret
+ (wrapper.instance() as App).handleUpdate(newValuesWithSecret);
+ expect(updateWebhook).toHaveBeenLastCalledWith(newValuesWithSecret);
+
+ await new Promise(setImmediate);
+ wrapper.update();
+ expect(wrapper.state('webhooks')).toEqual([
+ { key: '1', name: 'foo', url: 'http://foo', hasSecret: false },
+ { key: '2', name: 'bar', url: 'http://bar', hasSecret: true },
+ ]);
+
+ // Without secret
+ (wrapper.instance() as App).handleUpdate(newValuesWithoutSecret);
+ expect(updateWebhook).toHaveBeenLastCalledWith(newValuesWithoutSecret);
+
+ await new Promise(setImmediate);
+ wrapper.update();
+ expect(wrapper.state('webhooks')).toEqual([
+ { key: '1', name: 'foo', url: 'http://foo', hasSecret: false },
+ { key: '2', name: 'bar', url: 'http://bar', hasSecret: true },
+ ]);
+
+ // With empty secret
+ (wrapper.instance() as App).handleUpdate(newValuesWithEmptySecret);
+ expect(updateWebhook).toHaveBeenLastCalledWith(newValuesWithEmptySecret);
+
+ await new Promise(setImmediate);
+ wrapper.update();
+ expect(wrapper.state('webhooks')).toEqual([
+ { key: '1', name: 'foo', url: 'http://foo', hasSecret: false },
+ { key: '2', name: 'bar', url: 'http://bar', hasSecret: false },
]);
});
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/CreateWebhookForm-it.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/CreateWebhookForm-it.tsx
new file mode 100644
index 00000000000..c56875897d3
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/CreateWebhookForm-it.tsx
@@ -0,0 +1,145 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import userEvent from '@testing-library/user-event';
+import * as React from 'react';
+import { byLabelText, byRole } from 'testing-library-selector';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import CreateWebhookForm from '../CreateWebhookForm';
+
+const ui = {
+ nameInput: byRole('textbox', { name: 'webhooks.name field_required' }),
+ urlInput: byRole('textbox', { name: 'webhooks.url field_required' }),
+ secretInput: byLabelText('webhooks.secret'),
+ secretInputMaskButton: byRole('button', { name: 'webhooks.secret.field_mask.link' }),
+ createButton: byRole('button', { name: 'create' }),
+ updateButton: byRole('button', { name: 'update_verb' }),
+};
+
+describe('Webhook form', () => {
+ it('should correctly submit creation form', async () => {
+ const user = userEvent.setup();
+ const webhook = {
+ name: 'foo',
+ url: 'http://bar',
+ secret: '',
+ };
+ const onDone = jest.fn();
+
+ renderCreateWebhookForm({ onDone });
+
+ expect(ui.nameInput.get()).toHaveValue('');
+ expect(ui.urlInput.get()).toHaveValue('');
+ expect(ui.secretInput.get()).toHaveValue('');
+ expect(ui.createButton.get()).toBeDisabled();
+
+ await user.type(ui.nameInput.get(), webhook.name);
+ await user.type(ui.urlInput.get(), webhook.url);
+ expect(ui.createButton.get()).toBeEnabled();
+
+ await user.click(ui.createButton.get());
+ expect(onDone).toHaveBeenCalledWith(webhook);
+ });
+
+ it('should correctly submit update form', async () => {
+ const user = userEvent.setup();
+ const webhook = {
+ hasSecret: false,
+ key: 'test-webhook-key',
+ name: 'foo',
+ url: 'http://bar',
+ };
+ const nameExtension = 'bar';
+ const url = 'http://bar';
+ const onDone = jest.fn();
+
+ renderCreateWebhookForm({ onDone, webhook });
+
+ expect(ui.nameInput.get()).toHaveValue(webhook.name);
+ expect(ui.urlInput.get()).toHaveValue(webhook.url);
+ expect(ui.secretInput.query()).not.toBeInTheDocument();
+ expect(ui.secretInputMaskButton.get()).toBeInTheDocument();
+ expect(ui.updateButton.get()).toBeDisabled();
+
+ await user.type(ui.nameInput.get(), nameExtension);
+ await user.clear(ui.urlInput.get());
+ await user.type(ui.urlInput.get(), url);
+ expect(ui.updateButton.get()).toBeEnabled();
+
+ await user.click(ui.updateButton.get());
+ expect(onDone).toHaveBeenCalledWith({
+ name: `${webhook.name}${nameExtension}`,
+ url,
+ secret: undefined,
+ });
+ });
+
+ it('should correctly submit update form with empty secret', async () => {
+ const user = userEvent.setup();
+ const webhook = {
+ hasSecret: false,
+ key: 'test-webhook-key',
+ name: 'foo',
+ url: 'http://bar',
+ };
+ const onDone = jest.fn();
+
+ renderCreateWebhookForm({ onDone, webhook });
+
+ await user.click(ui.secretInputMaskButton.get());
+ expect(ui.updateButton.get()).toBeEnabled();
+
+ await user.click(ui.updateButton.get());
+ expect(onDone).toHaveBeenCalledWith({
+ name: webhook.name,
+ url: webhook.url,
+ secret: '',
+ });
+ });
+
+ it('should correctly submit update form with updated secret', async () => {
+ const user = userEvent.setup();
+ const webhook = {
+ hasSecret: false,
+ key: 'test-webhook-key',
+ name: 'foo',
+ url: 'http://bar',
+ };
+ const secret = 'test-webhook-secret';
+ const onDone = jest.fn();
+
+ renderCreateWebhookForm({ onDone, webhook });
+
+ await user.click(ui.secretInputMaskButton.get());
+ await user.type(ui.secretInput.get(), secret);
+
+ await user.click(ui.updateButton.get());
+ expect(onDone).toHaveBeenCalledWith({
+ name: webhook.name,
+ url: webhook.url,
+ secret,
+ });
+ });
+});
+
+function renderCreateWebhookForm(props = {}) {
+ return renderComponent(
+ <CreateWebhookForm onClose={jest.fn()} onDone={jest.fn(() => Promise.resolve())} {...props} />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/CreateWebhookForm-test.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/CreateWebhookForm-test.tsx
deleted file mode 100644
index c8ec8b5ff71..00000000000
--- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/CreateWebhookForm-test.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { shallow } from 'enzyme';
-import * as React from 'react';
-import CreateWebhookForm from '../CreateWebhookForm';
-
-const webhookWithoutSecret = { key: '1', name: 'foo', url: 'http://foo.bar' };
-const webhookWithSecret = { key: '2', name: 'bar', secret: 'sonar', url: 'http://foo.bar' };
-
-it('should render correctly when creating a new webhook', () => {
- expect(getWrapper()).toMatchSnapshot();
-});
-
-it('should render correctly when updating a webhook without secret', () => {
- expect(getWrapper({ webhook: webhookWithoutSecret })).toMatchSnapshot();
-});
-
-it('should render correctly when updating a webhook with a secret', () => {
- expect(getWrapper({ webhook: webhookWithSecret })).toMatchSnapshot();
-});
-
-function getWrapper(props = {}) {
- return shallow(
- <CreateWebhookForm onClose={jest.fn()} onDone={jest.fn(() => Promise.resolve())} {...props} />
- );
-}
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/DeliveriesForm-test.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/DeliveriesForm-test.tsx
index 7a04114d953..35f1d6da9aa 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/DeliveriesForm-test.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/DeliveriesForm-test.tsx
@@ -50,7 +50,7 @@ jest.mock('../../../../api/webhooks', () => ({
),
}));
-const webhook = { key: '1', name: 'foo', url: 'http://foo.bar' };
+const webhook = { key: '1', name: 'foo', url: 'http://foo.bar', hasSecret: false };
beforeEach(() => {
(searchDeliveries as jest.Mock<any>).mockClear();
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/LatestDeliveryForm-test.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/LatestDeliveryForm-test.tsx
index 0aac1590d78..83756ec0ee7 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/LatestDeliveryForm-test.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/LatestDeliveryForm-test.tsx
@@ -38,7 +38,7 @@ const delivery = {
success: true,
};
-const webhook = { key: '1', name: 'foo', url: 'http://foo.bar' };
+const webhook = { key: '1', name: 'foo', url: 'http://foo.bar', hasSecret: false };
beforeEach(() => {
(getDelivery as jest.Mock<any>).mockClear();
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookActions-test.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookActions-test.tsx
index 6c5bb8f0616..f9e3da4f89f 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookActions-test.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookActions-test.tsx
@@ -26,6 +26,7 @@ const webhook = {
key: '1',
name: 'foo',
url: 'http://foo.bar',
+ hasSecret: false,
};
const delivery = {
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookItem-test.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookItem-test.tsx
index 1e5361f5b46..7741671cd32 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookItem-test.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookItem-test.tsx
@@ -21,10 +21,18 @@ import { shallow } from 'enzyme';
import * as React from 'react';
import WebhookItem from '../WebhookItem';
-const webhook = {
+const webhookWithoutSecret = {
key: '1',
name: 'my webhook',
url: 'http://webhook.target',
+ hasSecret: false,
+};
+
+const webhookWithSecret = {
+ key: '1',
+ name: 'my webhook',
+ url: 'http://webhook.target',
+ hasSecret: true,
};
it('should render correctly', () => {
@@ -33,7 +41,16 @@ it('should render correctly', () => {
<WebhookItem
onDelete={jest.fn(() => Promise.resolve())}
onUpdate={jest.fn(() => Promise.resolve())}
- webhook={webhook}
+ webhook={webhookWithoutSecret}
+ />
+ )
+ ).toMatchSnapshot();
+ expect(
+ shallow(
+ <WebhookItem
+ onDelete={jest.fn(() => Promise.resolve())}
+ onUpdate={jest.fn(() => Promise.resolve())}
+ webhook={webhookWithSecret}
/>
)
).toMatchSnapshot();
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookItemLatestDelivery-test.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookItemLatestDelivery-test.tsx
index 312bb572b7d..ea960a1e764 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookItemLatestDelivery-test.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookItemLatestDelivery-test.tsx
@@ -34,6 +34,7 @@ const webhook = {
key: '1',
name: 'my webhook',
url: 'http://webhook.target',
+ hasSecret: false,
latestDelivery,
};
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhooksList-test.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhooksList-test.tsx
index 15210f6c747..2f3bcace219 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhooksList-test.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhooksList-test.tsx
@@ -22,8 +22,8 @@ import * as React from 'react';
import WebhooksList from '../WebhooksList';
const webhooks = [
- { key: '1', name: 'my webhook', url: 'http://webhook.target' },
- { key: '2', name: 'jenkins webhook', url: 'http://jenkins.target' },
+ { key: '1', name: 'my webhook', url: 'http://webhook.target', hasSecret: false },
+ { key: '2', name: 'jenkins webhook', url: 'http://jenkins.target', hasSecret: false },
];
it('should correctly render empty webhook list', () => {
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/App-test.tsx.snap
index 7a0ea7fcc9f..ba0b2bb2a08 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/App-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/App-test.tsx.snap
@@ -59,11 +59,13 @@ exports[`should fetch webhooks and display them 1`] = `
webhooks={
[
{
+ "hasSecret": false,
"key": "1",
"name": "foo",
"url": "http://foo",
},
{
+ "hasSecret": false,
"key": "2",
"name": "bar",
"url": "http://bar",
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/CreateWebhookForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/CreateWebhookForm-test.tsx.snap
deleted file mode 100644
index e66235b6d61..00000000000
--- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/CreateWebhookForm-test.tsx.snap
+++ /dev/null
@@ -1,61 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly when creating a new webhook 1`] = `
-<ValidationModal
- confirmButtonText="create"
- header="webhooks.create"
- initialValues={
- {
- "name": "",
- "secret": "",
- "url": "",
- }
- }
- onClose={[MockFunction]}
- onSubmit={[MockFunction]}
- size="small"
- validate={[Function]}
->
- <Component />
-</ValidationModal>
-`;
-
-exports[`should render correctly when updating a webhook with a secret 1`] = `
-<ValidationModal
- confirmButtonText="update_verb"
- header="webhooks.update"
- initialValues={
- {
- "name": "bar",
- "secret": "sonar",
- "url": "http://foo.bar",
- }
- }
- onClose={[MockFunction]}
- onSubmit={[MockFunction]}
- size="small"
- validate={[Function]}
->
- <Component />
-</ValidationModal>
-`;
-
-exports[`should render correctly when updating a webhook without secret 1`] = `
-<ValidationModal
- confirmButtonText="update_verb"
- header="webhooks.update"
- initialValues={
- {
- "name": "foo",
- "secret": "",
- "url": "http://foo.bar",
- }
- }
- onClose={[MockFunction]}
- onSubmit={[MockFunction]}
- size="small"
- validate={[Function]}
->
- <Component />
-</ValidationModal>
-`;
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhookItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhookItem-test.tsx.snap
index cc748f5df22..6e31377430a 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhookItem-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhookItem-test.tsx.snap
@@ -15,6 +15,7 @@ exports[`should render correctly 1`] = `
<WebhookItemLatestDelivery
webhook={
{
+ "hasSecret": false,
"key": "1",
"name": "my webhook",
"url": "http://webhook.target",
@@ -23,13 +24,56 @@ exports[`should render correctly 1`] = `
/>
</td>
<td
- className="thin nowrap text-right"
+ className="sw-text-right"
>
<WebhookActions
onDelete={[MockFunction]}
onUpdate={[MockFunction]}
webhook={
{
+ "hasSecret": false,
+ "key": "1",
+ "name": "my webhook",
+ "url": "http://webhook.target",
+ }
+ }
+ />
+ </td>
+</tr>
+`;
+
+exports[`should render correctly 2`] = `
+<tr>
+ <td>
+ my webhook
+ </td>
+ <td>
+ http://webhook.target
+ </td>
+ <td>
+ yes
+ </td>
+ <td>
+ <WebhookItemLatestDelivery
+ webhook={
+ {
+ "hasSecret": true,
+ "key": "1",
+ "name": "my webhook",
+ "url": "http://webhook.target",
+ }
+ }
+ />
+ </td>
+ <td
+ className="sw-text-right"
+ >
+ <WebhookActions
+ onDelete={[MockFunction]}
+ onUpdate={[MockFunction]}
+ webhook={
+ {
+ "hasSecret": true,
"key": "1",
"name": "my webhook",
"url": "http://webhook.target",
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhooksList-test.tsx.snap b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhooksList-test.tsx.snap
index cf08f013601..07ad28daa69 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhooksList-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhooksList-test.tsx.snap
@@ -24,7 +24,11 @@ exports[`should correctly render the webhooks 1`] = `
<th>
webhooks.last_execution
</th>
- <th />
+ <th
+ className="sw-text-right"
+ >
+ actions
+ </th>
</tr>
</thead>
<tbody>
@@ -34,6 +38,7 @@ exports[`should correctly render the webhooks 1`] = `
onUpdate={[MockFunction]}
webhook={
{
+ "hasSecret": false,
"key": "2",
"name": "jenkins webhook",
"url": "http://jenkins.target",
@@ -46,6 +51,7 @@ exports[`should correctly render the webhooks 1`] = `
onUpdate={[MockFunction]}
webhook={
{
+ "hasSecret": false,
"key": "1",
"name": "my webhook",
"url": "http://webhook.target",