From 827381f0e4e80b69ba67cd0128f47166eda916be Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Fri, 2 Feb 2018 11:34:03 +0100 Subject: [PATCH] SONAR-10345 Add webhooks management actions * SONAR-10345 Add the webhooks create/update form * SONAR-10345 Add the webhooks delete action * SONAR-10345 Add fields validation on webhook create page --- server/sonar-web/package.json | 1 + server/sonar-web/src/main/js/api/webhooks.ts | 25 +++- .../main/js/app/styles/components/modals.css | 43 +++++- .../main/js/apps/webhooks/components/App.tsx | 65 +++++++-- .../webhooks/components/CreateWebhookForm.tsx | 131 ++++++++++++++++++ .../apps/webhooks/components/PageActions.tsx | 89 ++++++++++++ .../apps/webhooks/components/PageHeader.tsx | 5 +- .../webhooks/components/WebhookActions.tsx | 105 ++++++++++++++ .../apps/webhooks/components/WebhookItem.tsx | 8 +- .../apps/webhooks/components/WebhooksList.tsx | 12 +- .../__tests__/CreateWebhookForm-test.tsx | 38 +++++ .../components/__tests__/PageActions-test.tsx | 51 +++++++ .../components/__tests__/PageHeader-test.tsx | 8 +- .../__tests__/WebhookActions-test.tsx | 66 +++++++++ .../components/__tests__/WebhookItem-test.tsx | 10 +- .../__tests__/WebhooksList-test.tsx | 15 +- .../__tests__/__snapshots__/App-test.tsx.snap | 18 ++- .../CreateWebhookForm-test.tsx.snap | 35 +++++ .../__snapshots__/PageActions-test.tsx.snap | 33 +++++ .../__snapshots__/PageHeader-test.tsx.snap | 1 + .../WebhookActions-test.tsx.snap | 24 ++++ .../__snapshots__/WebhookItem-test.tsx.snap | 15 ++ .../__snapshots__/WebhooksList-test.tsx.snap | 5 + .../js/components/controls/ConfirmButton.tsx | 4 +- .../controls/InputValidationField.tsx | 52 +++++++ .../controls/ModalValidationField.tsx | 49 +++++++ .../components/controls/ValidationModal.tsx | 108 +++++++++++++++ .../__tests__/InputValidationField-test.tsx | 44 ++++++ .../__tests__/ModalValidationField-test.tsx | 48 +++++++ .../__tests__/ValidationModal-test.tsx | 67 +++++++++ .../InputValidationField-test.tsx.snap | 11 ++ .../ModalValidationField-test.tsx.snap | 73 ++++++++++ .../ValidationModal-test.tsx.snap | 68 +++++++++ .../icons-components/AlertSuccessIcon.tsx | 40 ++++++ server/sonar-web/yarn.lock | 22 +++ .../resources/org/sonar/l10n/core.properties | 11 ++ 36 files changed, 1374 insertions(+), 26 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx create mode 100644 server/sonar-web/src/main/js/apps/webhooks/components/PageActions.tsx create mode 100644 server/sonar-web/src/main/js/apps/webhooks/components/WebhookActions.tsx create mode 100644 server/sonar-web/src/main/js/apps/webhooks/components/__tests__/CreateWebhookForm-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/webhooks/components/__tests__/PageActions-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookActions-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/CreateWebhookForm-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageActions-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhookActions-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/controls/InputValidationField.tsx create mode 100644 server/sonar-web/src/main/js/components/controls/ModalValidationField.tsx create mode 100644 server/sonar-web/src/main/js/components/controls/ValidationModal.tsx create mode 100644 server/sonar-web/src/main/js/components/controls/__tests__/InputValidationField-test.tsx create mode 100644 server/sonar-web/src/main/js/components/controls/__tests__/ModalValidationField-test.tsx create mode 100644 server/sonar-web/src/main/js/components/controls/__tests__/ValidationModal-test.tsx create mode 100644 server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/InputValidationField-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ModalValidationField-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ValidationModal-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/icons-components/AlertSuccessIcon.tsx diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json index 56658e361e8..ac465a81a0b 100644 --- a/server/sonar-web/package.json +++ b/server/sonar-web/package.json @@ -18,6 +18,7 @@ "d3-shape": "1.2.0", "date-fns": "1.29.0", "escape-html": "1.0.3", + "formik": "0.11.7", "handlebars": "2.0.0", "history": "3.3.0", "intl-relativeformat": "2.1.0", diff --git a/server/sonar-web/src/main/js/api/webhooks.ts b/server/sonar-web/src/main/js/api/webhooks.ts index 547702464b1..f23cb2f079b 100644 --- a/server/sonar-web/src/main/js/api/webhooks.ts +++ b/server/sonar-web/src/main/js/api/webhooks.ts @@ -17,13 +17,34 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { getJSON } from '../helpers/request'; +import { getJSON, post, postJSON } from '../helpers/request'; import throwGlobalError from '../app/utils/throwGlobalError'; import { Webhook } from '../app/types'; +export function createWebhook(data: { + name: string; + organization: string | undefined; + project?: string; + url: string; +}): Promise<{ webhook: Webhook }> { + return postJSON('/api/webhooks/create', data).catch(throwGlobalError); +} + +export function deleteWebhook(data: { webhook: string }): Promise { + return post('/api/webhooks/delete', data).catch(throwGlobalError); +} + export function searchWebhooks(data: { organization: string | undefined; project?: string; }): Promise<{ webhooks: Webhook[] }> { - return getJSON('/api/webhooks/search', data).catch(throwGlobalError); + return getJSON('/api/webhooks/list', data).catch(throwGlobalError); +} + +export function updateWebhook(data: { + webhook: string; + name: string; + url: string; +}): Promise { + return post('/api/webhooks/update', data).catch(throwGlobalError); } diff --git a/server/sonar-web/src/main/js/app/styles/components/modals.css b/server/sonar-web/src/main/js/app/styles/components/modals.css index 3bf2f396559..dbe8a64cb98 100644 --- a/server/sonar-web/src/main/js/app/styles/components/modals.css +++ b/server/sonar-web/src/main/js/app/styles/components/modals.css @@ -121,19 +121,24 @@ ul.modal-head-metadata li { height: auto; } -.modal-field { +.modal-field, +.modal-large-field, +.modal-validation-field { clear: both; display: block; padding: 5px 0 5px 130px; } .modal-large-field { - clear: both; - display: block; padding: 20px 40px; } -.modal-field label { +.modal-validation-field { + padding: 3px 0 3px 130px; +} + +.modal-field label, +.modal-validation-field label { position: relative; left: -140px; display: block; @@ -149,7 +154,8 @@ ul.modal-head-metadata li { text-overflow: ellipsis; } -.modal-field label.simple-label { +.modal-field label.simple-label, +.modal-validation-field label.simple-label { display: inline-block; vertical-align: middle; float: none; @@ -215,6 +221,29 @@ ul.modal-head-metadata li { width: 100%; } +.modal-validation-field input, +.modal-validation-field textarea, +.modal-validation-field .Select { + margin-right: 5px; + margin-bottom: 2px; + width: 250px; +} + +.modal-validation-field input:not(.has-error), +.modal-validation-field .Select:not(.has-error) { + margin-bottom: 18px; +} + +.modal-validation-field .has-error, +.modal-validation-field .has-error > .Select-control { + border-color: var(--red); +} + +.modal-validation-field .is-valid, +.modal-validation-field .is-valid > .Select-control { + border-color: var(--green); +} + .modal-field-description { padding-bottom: 4px; line-height: 1.4; @@ -224,6 +253,10 @@ ul.modal-head-metadata li { text-overflow: ellipsis; } +.modal-validation-field .modal-field-description { + margin-top: 2px; +} + .modal-foot { line-height: var(--controlHeight); padding: 10px; 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 e1d559ef307..d48a3690400 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 @@ -19,9 +19,10 @@ */ import * as React from 'react'; import { Helmet } from 'react-helmet'; +import PageActions from './PageActions'; import PageHeader from './PageHeader'; import WebhooksList from './WebhooksList'; -import { searchWebhooks } from '../../../api/webhooks'; +import { createWebhook, deleteWebhook, searchWebhooks, updateWebhook } from '../../../api/webhooks'; import { LightComponent, Organization, Webhook } from '../../../app/types'; import { translate } from '../../../helpers/l10n'; @@ -48,12 +49,8 @@ export default class App extends React.PureComponent { this.mounted = false; } - fetchWebhooks = ({ organization, component } = this.props) => { - this.setState({ loading: true }); - searchWebhooks({ - organization: organization && organization.key, - project: component && component.key - }).then( + fetchWebhooks = () => { + return searchWebhooks(this.getScopeParams()).then( ({ webhooks }) => { if (this.mounted) { this.setState({ loading: false, webhooks }); @@ -67,15 +64,65 @@ export default class App extends React.PureComponent { ); }; + getScopeParams = ({ organization, component } = this.props) => { + return { organization: organization && organization.key, project: component && component.key }; + }; + + handleCreate = (data: { name: string; url: string }) => { + return createWebhook({ + ...data, + ...this.getScopeParams() + }).then(({ webhook }) => { + if (this.mounted) { + this.setState(({ webhooks }) => ({ webhooks: [...webhooks, webhook] })); + } + }); + }; + + handleDelete = (webhook: string) => { + return deleteWebhook({ webhook }).then(() => { + if (this.mounted) { + this.setState(({ webhooks }) => ({ + webhooks: webhooks.filter(item => item.key !== webhook) + })); + } + }); + }; + + handleUpdate = (data: { webhook: string; name: string; url: string }) => { + return updateWebhook(data).then(() => { + if (this.mounted) { + this.setState(({ webhooks }) => ({ + webhooks: webhooks.map( + webhook => (webhook.key === data.webhook ? { ...webhook, ...data } : webhook) + ) + })); + } + }); + }; + render() { const { loading, webhooks } = this.state; + return (
- + + + + + {!loading && (
- +
)}
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 new file mode 100644 index 00000000000..91a12c2ba7c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx @@ -0,0 +1,131 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { FormikProps } from 'formik'; +import ValidationModal from '../../../components/controls/ValidationModal'; +import InputValidationField from '../../../components/controls/InputValidationField'; +import { Webhook } from '../../../app/types'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + onClose: () => void; + onDone: (data: { name: string; url: string }) => Promise; + webhook?: Webhook; +} + +interface Values { + name: string; + url: string; +} + +export default class CreateWebhookForm extends React.PureComponent { + handleCancelClick = (event: React.SyntheticEvent) => { + event.preventDefault(); + event.currentTarget.blur(); + this.props.onClose(); + }; + + handleValidate = (data: Values) => { + const { name, url } = data; + const errors: { name?: string; url?: string } = {}; + if (!name.trim()) { + errors.name = translate('webhooks.name.required'); + } + if (!url.trim()) { + errors.url = translate('webhooks.url.required'); + } else if (!url.startsWith('http://') && !url.startsWith('https://')) { + errors.url = translate('webhooks.url.bad_protocol'); + } else if (url.indexOf(':', 6) > 0 && url.indexOf('@') <= 0) { + errors.url = translate('webhooks.url.bad_auth'); + } + return errors; + }; + + render() { + const { webhook } = this.props; + const isUpdate = !!webhook; + const modalHeader = isUpdate ? translate('webhooks.update') : translate('webhooks.create'); + const confirmButtonText = isUpdate ? translate('update_verb') : translate('create'); + return ( + + {({ + dirty, + errors, + handleBlur, + handleChange, + isSubmitting, + touched, + values + }: FormikProps) => ( + <> + + {translate('webhooks.name')} + * + + } + name="name" + onBlur={handleBlur} + onChange={handleChange} + touched={touched.name} + type="text" + value={values.name} + /> + + {translate('webhooks.url')} + * + + } + name="url" + onBlur={handleBlur} + onChange={handleChange} + touched={touched.url} + type="text" + value={values.url} + /> + + )} + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/PageActions.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/PageActions.tsx new file mode 100644 index 00000000000..92df63c688b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/webhooks/components/PageActions.tsx @@ -0,0 +1,89 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import CreateWebhookForm from './CreateWebhookForm'; +import Tooltip from '../../../components/controls/Tooltip'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; + +interface Props { + loading: boolean; + onCreate: (data: { name: string; url: string }) => Promise; + webhooksCount: number; +} + +interface State { + openCreate: boolean; +} + +const WEBHOOKS_LIMIT = 10; + +export default class PageActions extends React.PureComponent { + mounted: boolean = false; + state: State = { openCreate: false }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleCreateClose = () => { + if (this.mounted) { + this.setState({ openCreate: false }); + } + }; + + handleCreateOpen = () => { + this.setState({ openCreate: true }); + }; + + renderCreate = () => { + if (this.props.webhooksCount >= WEBHOOKS_LIMIT) { + return ( + + + + ); + } + + return ( + <> + + {this.state.openCreate && ( + + )} + + ); + }; + + render() { + if (this.props.loading) { + return null; + } + + return
{this.renderCreate()}
; + } +} diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/PageHeader.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/PageHeader.tsx index 229b835e5e9..2a23425df06 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/PageHeader.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/PageHeader.tsx @@ -22,15 +22,18 @@ import { FormattedMessage } from 'react-intl'; import { translate } from '../../../helpers/l10n'; interface Props { + children?: React.ReactNode; loading: boolean; } -export default function PageHeader({ loading }: Props) { +export default function PageHeader({ children, loading }: Props) { return (

{translate('webhooks.page')}

{loading && } + {children} +

Promise; + onUpdate: (data: { webhook: string; name: string; url: string }) => Promise; + webhook: Webhook; +} + +interface State { + updating: boolean; +} + +export default class WebhookActions extends React.PureComponent { + mounted: boolean = false; + state: State = { updating: false }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleDelete = () => { + return this.props.onDelete(this.props.webhook.key); + }; + + handleUpdate = (data: { name: string; url: string }) => { + return this.props.onUpdate({ ...data, webhook: this.props.webhook.key }); + }; + + handleUpdateClick = () => { + this.setState({ updating: true }); + }; + + handleUpdatingStop = () => { + this.setState({ updating: false }); + }; + + render() { + const { webhook } = this.props; + + return ( + <> + + + {translate('update_verb')} + + + + {({ onClick }) => ( + + {translate('delete')} + + )} + + + + {this.state.updating && ( + + )} + + ); + } +} 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 f156900d581..b51ceb085af 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 @@ -18,17 +18,23 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import WebhookActions from './WebhookActions'; import { Webhook } from '../../../app/types'; interface Props { + onDelete: (webhook: string) => Promise; + onUpdate: (data: { webhook: string; name: string; url: string }) => Promise; webhook: Webhook; } -export default function WebhookItem({ webhook }: Props) { +export default function WebhookItem({ onDelete, onUpdate, webhook }: Props) { return ( {webhook.name} {webhook.url} + + + ); } 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 600fad4e8d3..6bfaea6527b 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 @@ -23,6 +23,8 @@ import { Webhook } from '../../../app/types'; import { translate } from '../../../helpers/l10n'; interface Props { + onDelete: (webhook: string) => Promise; + onUpdate: (data: { webhook: string; name: string; url: string }) => Promise; webhooks: Webhook[]; } @@ -32,6 +34,7 @@ export default class WebhooksList extends React.PureComponent { {translate('name')} {translate('webhooks.url')} + ); @@ -45,7 +48,14 @@ export default class WebhooksList extends React.PureComponent { {this.renderHeader()} - {webhooks.map(webhook => )} + {webhooks.map(webhook => ( + + ))}
); 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 new file mode 100644 index 00000000000..a5e20a63282 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/CreateWebhookForm-test.tsx @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import CreateWebhookForm from '../CreateWebhookForm'; + +const webhook = { key: '1', name: 'foo', url: 'http://foo.bar' }; + +it('should render correctly when creating a new webhook', () => { + expect(getWrapper()).toMatchSnapshot(); +}); + +it('should render correctly when updating a webhook', () => { + expect(getWrapper({ webhook })).toMatchSnapshot(); +}); + +function getWrapper(props = {}) { + return shallow( + Promise.resolve())} {...props} /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/PageActions-test.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/PageActions-test.tsx new file mode 100644 index 00000000000..f50a9b062a4 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/PageActions-test.tsx @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import PageActions from '../PageActions'; +import { click } from '../../../../helpers/testUtils'; + +it('should render correctly', () => { + expect(getWrapper()).toMatchSnapshot(); +}); + +it('should not render', () => { + expect(getWrapper({ loading: true }).type()).toBeNull(); +}); + +it('should not allow to create a new webhook', () => { + expect(getWrapper({ webhooksCount: 10 })).toMatchSnapshot(); +}); + +it('should display the create form', () => { + const onCreate = jest.fn(); + const wrapper = getWrapper({ onCreate }); + click(wrapper.find('.js-webhook-create')); + expect(wrapper.find('CreateWebhookForm').exists()).toBeTruthy(); + wrapper.find('CreateWebhookForm').prop('onDone')({ + name: 'foo', + url: 'http://foo.bar' + }); + expect(onCreate).lastCalledWith({ name: 'foo', url: 'http://foo.bar' }); +}); + +function getWrapper(props = {}) { + return shallow(); +} diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/PageHeader-test.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/PageHeader-test.tsx index 148d1570cd4..055228c98a2 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/PageHeader-test.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/PageHeader-test.tsx @@ -22,5 +22,11 @@ import { shallow } from 'enzyme'; import PageHeader from '../PageHeader'; it('should render correctly', () => { - expect(shallow()).toMatchSnapshot(); + expect( + shallow( + +

+ + ) + ).toMatchSnapshot(); }); 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 new file mode 100644 index 00000000000..51aa702e132 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookActions-test.tsx @@ -0,0 +1,66 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import WebhookActions from '../WebhookActions'; +import { click } from '../../../../helpers/testUtils'; + +const webhook = { key: '1', name: 'foo', url: 'http://foo.bar' }; + +it('should render correctly', () => { + expect(getWrapper()).toMatchSnapshot(); +}); + +it('should display the update webhook form', () => { + const onUpdate = jest.fn(() => Promise.resolve()); + const wrapper = getWrapper({ onUpdate }); + click(wrapper.find('.js-webhook-update')); + expect(wrapper.find('CreateWebhookForm').exists()).toBeTruthy(); + wrapper.find('CreateWebhookForm').prop('onDone')({ + name: webhook.name, + url: webhook.url + }); + expect(onUpdate).lastCalledWith({ webhook: webhook.key, name: webhook.name, url: webhook.url }); +}); + +it('should display the delete webhook form', () => { + const onDelete = jest.fn(() => Promise.resolve()); + const wrapper = getWrapper({ onDelete }); + click( + wrapper + .find('ConfirmButton') + .dive() + .find('.js-webhook-delete') + ); + expect(wrapper.find('ConfirmButton').exists()).toBeTruthy(); + wrapper.find('ConfirmButton').prop('onConfirm')(); + expect(onDelete).lastCalledWith(webhook.key); +}); + +function getWrapper(props = {}) { + return shallow( + Promise.resolve())} + onUpdate={jest.fn(() => Promise.resolve())} + webhook={webhook} + {...props} + /> + ); +} 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 ab0a1a5d0e4..9322cf58e0a 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 @@ -24,5 +24,13 @@ import WebhookItem from '../WebhookItem'; const webhook = { key: '1', name: 'my webhook', url: 'http://webhook.target' }; it('should render correctly', () => { - expect(shallow()).toMatchSnapshot(); + expect( + shallow( + Promise.resolve())} + onUpdate={jest.fn(() => Promise.resolve())} + webhook={webhook} + /> + ) + ).toMatchSnapshot(); }); 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 e0337cc8f5c..531eaeded8f 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 @@ -27,9 +27,20 @@ const webhooks = [ ]; it('should correctly render empty webhook list', () => { - expect(shallow()).toMatchSnapshot(); + expect(getWrapper({ webhooks: [] })).toMatchSnapshot(); }); it('should correctly render the webhooks', () => { - expect(shallow()).toMatchSnapshot(); + expect(getWrapper()).toMatchSnapshot(); }); + +function getWrapper(props = {}) { + return shallow( + Promise.resolve())} + onUpdate={jest.fn(() => Promise.resolve())} + webhooks={webhooks} + {...props} + /> + ); +} 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 717e2e76bb0..3490f184a15 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 @@ -11,7 +11,13 @@ exports[`should be in loading status 1`] = ` /> + > + +
`; @@ -26,11 +32,19 @@ exports[`should fetch webhooks and display them 1`] = ` /> + > + +
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 new file mode 100644 index 00000000000..8ad194ad454 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/CreateWebhookForm-test.tsx.snap @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly when creating a new webhook 1`] = ` + +`; + +exports[`should render correctly when updating a webhook 1`] = ` + +`; diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageActions-test.tsx.snap new file mode 100644 index 00000000000..abbb9ca3fb9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageActions-test.tsx.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should not allow to create a new webhook 1`] = ` +
+ + + +
+`; + +exports[`should render correctly 1`] = ` +
+ + + +
+`; diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageHeader-test.tsx.snap index e40da7ba2d3..e44b8019fca 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageHeader-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageHeader-test.tsx.snap @@ -12,6 +12,7 @@ exports[`should render correctly 1`] = ` +

diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhookActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhookActions-test.tsx.snap new file mode 100644 index 00000000000..a82a5f3ed35 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhookActions-test.tsx.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` + + + + update_verb + + + + + +`; 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 b6968c76bed..7f45d646e0a 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 @@ -8,5 +8,20 @@ exports[`should render correctly 1`] = ` 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 01bcb34685a..ddf82de6e99 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 @@ -18,11 +18,14 @@ exports[`should correctly render the webhooks 1`] = ` webhooks.url + { disabled={submitting}> {confirmButtonText} - {translate('cancel')} + + {translate('cancel')} + )} diff --git a/server/sonar-web/src/main/js/components/controls/InputValidationField.tsx b/server/sonar-web/src/main/js/components/controls/InputValidationField.tsx new file mode 100644 index 00000000000..aae2cde6025 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/InputValidationField.tsx @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import * as classNames from 'classnames'; +import ModalValidationField from './ModalValidationField'; + +interface Props { + autoFocus?: boolean; + className?: string; + description?: string; + dirty: boolean; + disabled: boolean; + error: string | undefined; + id?: string; + label?: React.ReactNode; + name: string; + onBlur: (event: React.FocusEvent) => void; + onChange: (event: React.ChangeEvent) => void; + placeholder?: string; + touched: boolean; + type?: string; + value: string; +} + +export default function InputValidationField({ className, ...props }: Props) { + const { description, dirty, error, label, touched, ...inputProps } = props; + const modalValidationProps = { description, dirty, error, label, touched }; + return ( + + {({ className: validationClassName }) => ( + + )} + + ); +} diff --git a/server/sonar-web/src/main/js/components/controls/ModalValidationField.tsx b/server/sonar-web/src/main/js/components/controls/ModalValidationField.tsx new file mode 100644 index 00000000000..503b58952e3 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/ModalValidationField.tsx @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import * as classNames from 'classnames'; +import AlertErrorIcon from '../icons-components/AlertErrorIcon'; +import AlertSuccessIcon from '../icons-components/AlertSuccessIcon'; + +interface Props { + children: (props: { className?: string }) => React.ReactNode; + description?: string; + dirty: boolean; + error: string | undefined; + label?: React.ReactNode; + touched: boolean; +} + +export default function ModalValidationField(props: Props) { + const { description, dirty, error } = props; + + const isValid = dirty && props.touched && error === undefined; + const showError = dirty && props.touched && error !== undefined; + return ( +

+ {props.label} + {props.children({ className: classNames({ 'has-error': showError, 'is-valid': isValid }) })} + {showError && } + {isValid && } + {showError &&

{error}

} + {description &&
{description}
} +
+ ); +} diff --git a/server/sonar-web/src/main/js/components/controls/ValidationModal.tsx b/server/sonar-web/src/main/js/components/controls/ValidationModal.tsx new file mode 100644 index 00000000000..d52a0b31af8 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/ValidationModal.tsx @@ -0,0 +1,108 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { withFormik, Form, FormikActions, FormikProps } from 'formik'; +import Modal from './Modal'; +import DeferredSpinner from '../common/DeferredSpinner'; +import { translate } from '../../helpers/l10n'; + +interface InnerFormProps { + children: (props: FormikProps) => React.ReactNode; + confirmButtonText: string; + header: string; + initialValues: Values; +} + +interface Props extends InnerFormProps { + isInitialValid?: boolean; + onClose: () => void; + validate: (data: Values) => void | object | Promise; + onSubmit: (data: Values) => void | Promise; +} + +export default class ValidationModal extends React.PureComponent> { + handleCancelClick = (event: React.SyntheticEvent) => { + event.preventDefault(); + event.currentTarget.blur(); + this.props.onClose(); + }; + + handleSubmit = (data: Values, { setSubmitting }: FormikActions) => { + const result = this.props.onSubmit(data); + if (result) { + result.then( + () => { + setSubmitting(false); + this.props.onClose(); + }, + () => { + setSubmitting(false); + } + ); + } else { + setSubmitting(false); + this.props.onClose(); + } + }; + + render() { + const { header } = this.props; + + const InnerForm = withFormik, Values>({ + handleSubmit: this.handleSubmit, + isInitialValid: this.props.isInitialValid, + mapPropsToValues: props => props.initialValues, + validate: this.props.validate + })(props => ( +
+
+

{props.header}

+
+ +
{props.children(props)}
+ +
+ + + +
+
+ )); + + return ( + + + {this.props.children} + + + ); + } +} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/InputValidationField-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/InputValidationField-test.tsx new file mode 100644 index 00000000000..e0864a1b8fc --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/InputValidationField-test.tsx @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import InputValidationField from '../InputValidationField'; + +it('should render correctly', () => { + expect(getWrapper()).toMatchSnapshot(); +}); + +function getWrapper(props = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ModalValidationField-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/ModalValidationField-test.tsx new file mode 100644 index 00000000000..ae9a2f74118 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/ModalValidationField-test.tsx @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import ModalValidationField from '../ModalValidationField'; + +it('should display the field without any error/validation', () => { + expect(getWrapper({ description: 'Describe Foo.', touched: false })).toMatchSnapshot(); + expect(getWrapper({ dirty: false })).toMatchSnapshot(); +}); + +it('should display the field as valid', () => { + expect(getWrapper({ error: undefined })).toMatchSnapshot(); +}); + +it('should display the field with an error', () => { + expect(getWrapper()).toMatchSnapshot(); +}); + +function getWrapper(props = {}) { + return shallow( + Foo} + touched={true} + {...props}> + {({ className }) => } + + ); +} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ValidationModal-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/ValidationModal-test.tsx new file mode 100644 index 00000000000..f3048542aa1 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/ValidationModal-test.tsx @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import { FormikProps } from 'formik'; +import ValidationModal from '../ValidationModal'; + +it('should render correctly', () => { + const { wrapper, inner } = getWrapper(); + expect(wrapper).toMatchSnapshot(); + expect(inner).toMatchSnapshot(); +}); + +interface Values { + field: string; +} + +function getWrapper(props = {}) { + const wrapper = shallow( + ({ field: values.field.length < 2 && 'Too small' })} + onSubmit={jest.fn(() => Promise.resolve())} + {...props}> + {(props: FormikProps) => ( +
+ +
+ )} +
+ ); + return { + wrapper, + inner: wrapper + .childAt(0) + .dive() + .dive() + .dive() + }; +} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/InputValidationField-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/InputValidationField-test.tsx.snap new file mode 100644 index 00000000000..8afa4d9aa5a --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/InputValidationField-test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` + +`; diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ModalValidationField-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ModalValidationField-test.tsx.snap new file mode 100644 index 00000000000..dc901ce06f2 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ModalValidationField-test.tsx.snap @@ -0,0 +1,73 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should display the field as valid 1`] = ` +
+ + + +
+`; + +exports[`should display the field with an error 1`] = ` +
+ + + +

+ Is required +

+
+`; + +exports[`should display the field without any error/validation 1`] = ` +
+ + +
+ Describe Foo. +
+
+`; + +exports[`should display the field without any error/validation 2`] = ` +
+ + +
+`; diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ValidationModal-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ValidationModal-test.tsx.snap new file mode 100644 index 00000000000..898438ec774 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ValidationModal-test.tsx.snap @@ -0,0 +1,68 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` + + + +`; + +exports[`should render correctly 2`] = ` +
+
+

+ title +

+
+
+ + + +
+
+ + + +
+ +`; diff --git a/server/sonar-web/src/main/js/components/icons-components/AlertSuccessIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/AlertSuccessIcon.tsx new file mode 100644 index 00000000000..432461ad528 --- /dev/null +++ b/server/sonar-web/src/main/js/components/icons-components/AlertSuccessIcon.tsx @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import * as theme from '../../app/theme'; +import { IconProps } from './types'; + +export default function AlertSuccessIcon({ className, fill = theme.green, size = 16 }: IconProps) { + return ( + + + + ); +} diff --git a/server/sonar-web/yarn.lock b/server/sonar-web/yarn.lock index 7be3c394b57..4f6610a2e05 100644 --- a/server/sonar-web/yarn.lock +++ b/server/sonar-web/yarn.lock @@ -3173,6 +3173,16 @@ form-data@~2.3.1: combined-stream "^1.0.5" mime-types "^2.1.12" +formik@0.11.7: + version "0.11.7" + resolved "https://registry.yarnpkg.com/formik/-/formik-0.11.7.tgz#7b2c66a5546c793dfb07b39b965aef69dcd39326" + dependencies: + lodash.clonedeep "^4.5.0" + lodash.isequal "4.5.0" + lodash.topath "4.5.2" + prop-types "^15.5.10" + warning "^3.0.0" + forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -4807,6 +4817,10 @@ lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + lodash.cond@^4.3.0: version "4.5.2" resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5" @@ -4823,6 +4837,10 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" +lodash.isequal@4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + lodash.keys@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" @@ -4839,6 +4857,10 @@ lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" +lodash.topath@4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/lodash.topath/-/lodash.topath-4.5.2.tgz#3616351f3bba61994a0931989660bd03254fd009" + lodash.unescape@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 93542c18764..8c4b0f0c56b 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2814,7 +2814,18 @@ favorite.current.UTS=This test file is marked as favorite. # #------------------------------------------------------------------------------ webhooks.page=Webhooks +webhooks.create=Create Webhook +webhooks.delete=Delete Webhook +webhooks.delete.confirm=Are you sure you want to delete the webhook "{0}"? webhooks.description=Webhooks are used to notify external services when a project analysis is done. An HTTP POST request including a JSON payload is sent to each of the provided URLs. Learn more in the {url}. webhooks.documentation_link=Webhooks documentation +webhooks.maximum_reached=You reached your maximum number of {0} webhooks. You can still update or delete an existing one. +webhooks.name=Name +webhooks.name.required=Name is required. webhooks.no_result=No webhook defined. +webhooks.update=Update Webhook webhooks.url=URL +webhooks.url.bad_auth=Bad format of URL authentication. +webhooks.url.bad_protocol=URL must start with "http://" or "https://". +webhooks.url.description=Server endpoint that will receive the webhook payload, for example: "http://my_server/foo". If HTTP Basic authentication is used, HTTPS is recommended to avoid man in the middle attacks. Example: "https://myLogin:myPassword@my_server/foo" +webhooks.url.required=URL is required. \ No newline at end of file -- 2.39.5