aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2018-01-31 11:23:12 +0100
committerGuillaume Jambet <guillaume.jambet@gmail.com>2018-03-01 15:21:05 +0100
commit3c5c062c8bf27f5ed93c72871d9d9ddcf4c0c548 (patch)
tree0efb13547233d7d159a6732add6aa4d850349357 /server/sonar-web/src/main
parentee3a17fa0f5a597f7f8d76562cb8d15fc6b3b342 (diff)
downloadsonarqube-3c5c062c8bf27f5ed93c72871d9d9ddcf4c0c548.tar.gz
sonarqube-3c5c062c8bf27f5ed93c72871d9d9ddcf4c0c548.zip
SONAR-10344 Create the webhooks console page
* SONAR-10348 Create the webhooks console page * SONAR-10349 Add webhook console at global admin scope * SONAR-10349 Add webhook console at project scope * SONAR-10349 Add webhook console at organization scope
Diffstat (limited to 'server/sonar-web/src/main')
-rw-r--r--server/sonar-web/src/main/js/api/webhooks.ts43
-rw-r--r--server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx (renamed from server/sonar-web/src/main/js/app/components/ProjectAdminContainer.js)34
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx16
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap57
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx5
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap8
-rw-r--r--server/sonar-web/src/main/js/app/utils/startReactApp.js3
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap10
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/routes.ts4
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/App.tsx84
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/PageHeader.tsx52
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/WebhookItem.tsx34
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/WebhooksList.tsx58
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/App-test.tsx83
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/PageHeader-test.tsx26
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookItem-test.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhooksList-test.tsx35
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/App-test.tsx.snap38
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageHeader-test.tsx.snap35
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhookItem-test.tsx.snap12
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhooksList-test.tsx.snap46
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/routes.ts32
23 files changed, 731 insertions, 17 deletions
diff --git a/server/sonar-web/src/main/js/api/webhooks.ts b/server/sonar-web/src/main/js/api/webhooks.ts
new file mode 100644
index 00000000000..fa526855e02
--- /dev/null
+++ b/server/sonar-web/src/main/js/api/webhooks.ts
@@ -0,0 +1,43 @@
+/*
+ * 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 { getJSON } from '../helpers/request';
+import throwGlobalError from '../app/utils/throwGlobalError';
+
+export interface Delivery {
+ id: string;
+ at: string;
+ success: boolean;
+ httpStatus: number;
+ durationMs: number;
+}
+
+export interface Webhook {
+ key: string;
+ name: string;
+ url: string;
+ latestDelivery?: Delivery;
+}
+
+export function searchWebhooks(data: {
+ organization: string | undefined;
+ project?: string;
+}): Promise<{ webhooks: Webhook[] }> {
+ return getJSON('/api/webhooks/search', data).catch(throwGlobalError);
+}
diff --git a/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.js b/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx
index 2c70fb9401d..612f4bb043f 100644
--- a/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.js
+++ b/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx
@@ -17,20 +17,22 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import React from 'react';
+import * as React from 'react';
import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
+import { Component, Branch } from '../types';
-export default class ProjectAdminContainer extends React.PureComponent {
- /*::
- props: {
- component: {
- configuration?: {
- showSettings: boolean
- }
- }
- };
- */
+interface Props {
+ children: JSX.Element;
+ branch?: Branch;
+ branches: Branch[];
+ component: Component;
+ isInProgress?: boolean;
+ isPending?: boolean;
+ onBranchesChange: () => void;
+ onComponentChange: (changes: {}) => void;
+}
+export default class ProjectAdminContainer extends React.PureComponent<Props> {
componentDidMount() {
this.checkPermissions();
}
@@ -39,17 +41,17 @@ export default class ProjectAdminContainer extends React.PureComponent {
this.checkPermissions();
}
- isProjectAdmin() {
- const { configuration } = this.props.component;
- return configuration != null && configuration.showSettings;
- }
-
checkPermissions() {
if (!this.isProjectAdmin()) {
handleRequiredAuthorization();
}
}
+ isProjectAdmin() {
+ const { configuration } = this.props.component;
+ return configuration != null && configuration.showSettings;
+ }
+
render() {
if (!this.isProjectAdmin()) {
return null;
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
index 564a54ea318..bb56f7751a8 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
@@ -243,6 +243,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
this.renderPermissionsLink(),
this.renderBackgroundTasksLink(),
this.renderUpdateKeyLink(),
+ this.renderWebhooksLink(),
...this.renderAdminExtensions(),
this.renderDeletionLink()
];
@@ -394,6 +395,21 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
);
}
+ renderWebhooksLink() {
+ if (!this.getConfiguration().showSettings || !this.isProject()) {
+ return null;
+ }
+ return (
+ <li key="webhooks">
+ <Link
+ to={{ pathname: '/project/webhooks', query: { id: this.props.component.key } }}
+ activeClassName="active">
+ {translate('webhooks.page')}
+ </Link>
+ </li>
+ );
+ }
+
renderDeletionLink() {
const { qualifier } = this.props.component;
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap
index 56558732e74..592b1d44eee 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap
@@ -151,6 +151,25 @@ exports[`should work for all qualifiers 1`] = `
</Link>
</li>
<li
+ key="webhooks"
+ >
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/webhooks",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ webhooks.page
+ </Link>
+ </li>
+ <li
key="project_delete"
>
<Link
@@ -1061,6 +1080,25 @@ exports[`should work with extensions 1`] = `
</Link>
</li>
<li
+ key="webhooks"
+ >
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/webhooks",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ webhooks.page
+ </Link>
+ </li>
+ <li
key="foo"
>
<Link
@@ -1293,6 +1331,25 @@ exports[`should work with multiple extensions 1`] = `
</Link>
</li>
<li
+ key="webhooks"
+ >
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/webhooks",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ webhooks.page
+ </Link>
+ </li>
+ <li
key="foo"
>
<Link
diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx
index 32433c78d15..7f50c588689 100644
--- a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx
@@ -117,6 +117,11 @@ export default class SettingsNav extends React.PureComponent<Props> {
{translate('custom_metrics.page')}
</IndexLink>
</li>
+ <li>
+ <IndexLink to="/admin/webhooks" activeClassName="active">
+ {translate('webhooks.page')}
+ </IndexLink>
+ </li>
{extensionsWithoutSupport.map(this.renderExtension)}
</ul>
</li>
diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap
index 64822b179f6..a80573d7d9d 100644
--- a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap
@@ -55,6 +55,14 @@ exports[`should work with extensions 1`] = `
custom_metrics.page
</IndexLink>
</li>
+ <li>
+ <IndexLink
+ activeClassName="active"
+ to="/admin/webhooks"
+ >
+ webhooks.page
+ </IndexLink>
+ </li>
<li
key="foo"
>
diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.js b/server/sonar-web/src/main/js/app/utils/startReactApp.js
index c6af220da94..98968f52f68 100644
--- a/server/sonar-web/src/main/js/app/utils/startReactApp.js
+++ b/server/sonar-web/src/main/js/app/utils/startReactApp.js
@@ -74,6 +74,7 @@ import settingsRoutes from '../../apps/settings/routes';
import systemRoutes from '../../apps/system/routes';
import usersRoutes from '../../apps/users/routes';
import webAPIRoutes from '../../apps/web-api/routes';
+import webhooksRoutes from '../../apps/webhooks/routes';
import { maintenanceRoutes, setupRoutes } from '../../apps/maintenance/routes';
import { globalPermissionsRoutes, projectPermissionsRoutes } from '../../apps/permissions/routes';
@@ -212,6 +213,7 @@ const startReactApp = () => {
<Route path="project/branches" childRoutes={projectBranchesRoutes} />
<Route path="project/settings" childRoutes={settingsRoutes} />
<Route path="project_roles" childRoutes={projectPermissionsRoutes} />
+ <Route path="project/webhooks" childRoutes={webhooksRoutes} />
</Route>
{projectAdminRoutes}
</Route>
@@ -232,6 +234,7 @@ const startReactApp = () => {
<Route path="system" childRoutes={systemRoutes} />
<Route path="marketplace" childRoutes={marketplaceRoutes} />
<Route path="users" childRoutes={usersRoutes} />
+ <Route path="webhooks" childRoutes={webhooksRoutes} />
</Route>
</Route>
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx
index 0446e83c50d..cc2b9631ee8 100644
--- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx
@@ -93,6 +93,11 @@ export default function OrganizationNavigationAdministration({ location, organiz
</Link>
</li>
<li>
+ <Link to={`/organizations/${organization.key}/webhooks`} activeClassName="active">
+ {translate('webhooks.page')}
+ </Link>
+ </li>
+ <li>
<Link to={`/organizations/${organization.key}/edit`} activeClassName="active">
{translate('edit')}
</Link>
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap
index 713261b2290..c7764bf1c8b 100644
--- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap
@@ -63,6 +63,16 @@ exports[`renders 1`] = `
activeClassName="active"
onlyActiveOnIndex={false}
style={Object {}}
+ to="/organizations/foo/webhooks"
+ >
+ webhooks.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
to="/organizations/foo/edit"
>
edit
diff --git a/server/sonar-web/src/main/js/apps/organizations/routes.ts b/server/sonar-web/src/main/js/apps/organizations/routes.ts
index 2b11756dbc3..3277341cfb3 100644
--- a/server/sonar-web/src/main/js/apps/organizations/routes.ts
+++ b/server/sonar-web/src/main/js/apps/organizations/routes.ts
@@ -31,6 +31,7 @@ import ProjectManagementApp from '../projectsManagement/AppContainer';
import codingRulesRoutes from '../coding-rules/routes';
import qualityGatesRoutes from '../quality-gates/routes';
import qualityProfilesRoutes from '../quality-profiles/routes';
+import webhooksRoutes from '../webhooks/routes';
import Issues from '../issues/components/AppContainer';
import GroupsApp from '../groups/components/App';
import OrganizationPageExtension from '../../app/components/extensions/OrganizationPageExtension';
@@ -88,7 +89,8 @@ const routes = [
{ path: 'groups', component: GroupsApp },
{ path: 'permissions', component: GlobalPermissionsApp },
{ path: 'permission_templates', component: PermissionTemplateApp },
- { path: 'projects_management', component: ProjectManagementApp }
+ { path: 'projects_management', component: ProjectManagementApp },
+ { path: 'webhooks', childRoutes: webhooksRoutes }
]
}
]
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
new file mode 100644
index 00000000000..36f26f55913
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx
@@ -0,0 +1,84 @@
+/*
+ * 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 { Helmet } from 'react-helmet';
+import PageHeader from './PageHeader';
+import WebhooksList from './WebhooksList';
+import { searchWebhooks, Webhook } from '../../../api/webhooks';
+import { LightComponent, Organization } from '../../../app/types';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ organization: Organization | undefined;
+ component?: LightComponent;
+}
+
+interface State {
+ loading: boolean;
+ webhooks: Webhook[];
+}
+
+export default class App extends React.PureComponent<Props, State> {
+ mounted: boolean = false;
+ state: State = { loading: true, webhooks: [] };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchWebhooks();
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchWebhooks = ({ organization, component } = this.props) => {
+ this.setState({ loading: true });
+ searchWebhooks({
+ organization: organization && organization.key,
+ project: component && component.key
+ }).then(
+ ({ webhooks }) => {
+ if (this.mounted) {
+ this.setState({ loading: false, webhooks });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ render() {
+ const { loading, webhooks } = this.state;
+ return (
+ <div className="page page-limited">
+ <Helmet title={translate('webhooks.page')} />
+ <PageHeader loading={loading} />
+ {!loading && (
+ <div className="boxed-group boxed-group-inner">
+ <WebhooksList webhooks={webhooks} />
+ </div>
+ )}
+ </div>
+ );
+ }
+}
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
new file mode 100644
index 00000000000..229b835e5e9
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/PageHeader.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 { FormattedMessage } from 'react-intl';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ loading: boolean;
+}
+
+export default function PageHeader({ loading }: Props) {
+ return (
+ <header className="page-header">
+ <h1 className="page-title">{translate('webhooks.page')}</h1>
+ {loading && <i className="spinner" />}
+
+ <p className="page-description">
+ <FormattedMessage
+ defaultMessage={translate('webhooks.description')}
+ id={'webhooks.description'}
+ values={{
+ url: (
+ <a
+ href="https://redirect.sonarsource.com/doc/webhooks.html"
+ rel="noopener noreferrer"
+ target="_blank">
+ {translate('webhooks.documentation_link')}
+ </a>
+ )
+ }}
+ />
+ </p>
+ </header>
+ );
+}
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
new file mode 100644
index 00000000000..1be6a9da181
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItem.tsx
@@ -0,0 +1,34 @@
+/*
+ * 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 { Webhook } from '../../../api/webhooks';
+
+interface Props {
+ webhook: Webhook;
+}
+
+export default function WebhookItem({ webhook }: Props) {
+ return (
+ <tr>
+ <td>{webhook.name}</td>
+ <td>{webhook.url}</td>
+ </tr>
+ );
+}
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
new file mode 100644
index 00000000000..61689e75561
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/WebhooksList.tsx
@@ -0,0 +1,58 @@
+/*
+ * 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 WebhookItem from './WebhookItem';
+import { Webhook } from '../../../api/webhooks';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ webhooks: Webhook[];
+}
+
+export default class WebhooksList extends React.PureComponent<Props> {
+ renderHeader = () => (
+ <thead>
+ <tr>
+ <th>{translate('name')}</th>
+ <th>{translate('webhooks.url')}</th>
+ </tr>
+ </thead>
+ );
+
+ renderNoWebhooks = () => (
+ <tr>
+ <td>{translate('webhooks.no_result')}</td>
+ </tr>
+ );
+
+ render() {
+ const { webhooks } = this.props;
+ return (
+ <table className="data zebra">
+ {this.renderHeader()}
+ <tbody>
+ {webhooks.length > 0
+ ? webhooks.map(webhook => <WebhookItem key={webhook.key} webhook={webhook} />)
+ : this.renderNoWebhooks()}
+ </tbody>
+ </table>
+ );
+ }
+}
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
new file mode 100644
index 00000000000..085def84f20
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/App-test.tsx
@@ -0,0 +1,83 @@
+/*
+ * 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 App from '../App';
+import { searchWebhooks } from '../../../../api/webhooks';
+import { Visibility } from '../../../../app/types';
+
+jest.mock('../../../../api/webhooks', () => ({
+ searchWebhooks: jest.fn(() => Promise.resolve({ webhooks: [] }))
+}));
+
+const organization = { key: 'foo', name: 'Foo', projectVisibility: Visibility.Private };
+const component = { key: 'bar', organization: 'foo', qualifier: 'TRK' };
+
+beforeEach(() => {
+ (searchWebhooks as jest.Mock<any>).mockClear();
+});
+
+it('should be in loading status', () => {
+ expect(shallow(<App organization={undefined} />)).toMatchSnapshot();
+});
+
+it('should fetch webhooks and display them', async () => {
+ const wrapper = shallow(<App organization={organization} />);
+ expect(wrapper.state('loading')).toBeTruthy();
+
+ await new Promise(setImmediate);
+ expect(searchWebhooks).toHaveBeenCalledWith({ organization: organization.key });
+
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+});
+
+describe('should correctly fetch webhooks when', () => {
+ it('on global scope', async () => {
+ shallow(<App organization={undefined} />);
+
+ await new Promise(setImmediate);
+ expect(searchWebhooks).toHaveBeenCalledWith({ organization: undefined });
+ });
+
+ it('on project scope', async () => {
+ shallow(<App organization={undefined} component={component} />);
+
+ await new Promise(setImmediate);
+ expect(searchWebhooks).toHaveBeenCalledWith({ project: component.key });
+ });
+
+ it('on organization scope', async () => {
+ shallow(<App organization={organization} component={undefined} />);
+
+ await new Promise(setImmediate);
+ expect(searchWebhooks).toHaveBeenCalledWith({ organization: organization.key });
+ });
+
+ it('on project scope within an organization', async () => {
+ shallow(<App organization={organization} component={component} />);
+
+ await new Promise(setImmediate);
+ expect(searchWebhooks).toHaveBeenCalledWith({
+ organization: organization.key,
+ project: component.key
+ });
+ });
+});
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
new file mode 100644
index 00000000000..148d1570cd4
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/PageHeader-test.tsx
@@ -0,0 +1,26 @@
+/*
+ * 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 PageHeader from '../PageHeader';
+
+it('should render correctly', () => {
+ expect(shallow(<PageHeader loading={true} />)).toMatchSnapshot();
+});
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
new file mode 100644
index 00000000000..ab0a1a5d0e4
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookItem-test.tsx
@@ -0,0 +1,28 @@
+/*
+ * 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 WebhookItem from '../WebhookItem';
+
+const webhook = { key: '1', name: 'my webhook', url: 'http://webhook.target' };
+
+it('should render correctly', () => {
+ expect(shallow(<WebhookItem 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
new file mode 100644
index 00000000000..e0337cc8f5c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhooksList-test.tsx
@@ -0,0 +1,35 @@
+/*
+ * 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 WebhooksList from '../WebhooksList';
+
+const webhooks = [
+ { key: '1', name: 'my webhook', url: 'http://webhook.target' },
+ { key: '2', name: 'jenkins webhook', url: 'http://jenkins.target' }
+];
+
+it('should correctly render empty webhook list', () => {
+ expect(shallow(<WebhooksList webhooks={[]} />)).toMatchSnapshot();
+});
+
+it('should correctly render the webhooks', () => {
+ expect(shallow(<WebhooksList webhooks={webhooks} />)).toMatchSnapshot();
+});
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
new file mode 100644
index 00000000000..717e2e76bb0
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/App-test.tsx.snap
@@ -0,0 +1,38 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should be in loading status 1`] = `
+<div
+ className="page page-limited"
+>
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="webhooks.page"
+ />
+ <PageHeader
+ loading={true}
+ />
+</div>
+`;
+
+exports[`should fetch webhooks and display them 1`] = `
+<div
+ className="page page-limited"
+>
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="webhooks.page"
+ />
+ <PageHeader
+ loading={false}
+ />
+ <div
+ className="boxed-group boxed-group-inner"
+ >
+ <WebhooksList
+ webhooks={Array []}
+ />
+ </div>
+</div>
+`;
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
new file mode 100644
index 00000000000..e40da7ba2d3
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageHeader-test.tsx.snap
@@ -0,0 +1,35 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<header
+ className="page-header"
+>
+ <h1
+ className="page-title"
+ >
+ webhooks.page
+ </h1>
+ <i
+ className="spinner"
+ />
+ <p
+ className="page-description"
+ >
+ <FormattedMessage
+ defaultMessage="webhooks.description"
+ id="webhooks.description"
+ values={
+ Object {
+ "url": <a
+ href="https://redirect.sonarsource.com/doc/webhooks.html"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ webhooks.documentation_link
+ </a>,
+ }
+ }
+ />
+ </p>
+</header>
+`;
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
new file mode 100644
index 00000000000..b6968c76bed
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhookItem-test.tsx.snap
@@ -0,0 +1,12 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<tr>
+ <td>
+ my webhook
+ </td>
+ <td>
+ http://webhook.target
+ </td>
+</tr>
+`;
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
new file mode 100644
index 00000000000..01bcb34685a
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhooksList-test.tsx.snap
@@ -0,0 +1,46 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should correctly render empty webhook list 1`] = `
+<p>
+ webhooks.no_result
+</p>
+`;
+
+exports[`should correctly render the webhooks 1`] = `
+<table
+ className="data zebra"
+>
+ <thead>
+ <tr>
+ <th>
+ name
+ </th>
+ <th>
+ webhooks.url
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <WebhookItem
+ key="1"
+ webhook={
+ Object {
+ "key": "1",
+ "name": "my webhook",
+ "url": "http://webhook.target",
+ }
+ }
+ />
+ <WebhookItem
+ key="2"
+ webhook={
+ Object {
+ "key": "2",
+ "name": "jenkins webhook",
+ "url": "http://jenkins.target",
+ }
+ }
+ />
+ </tbody>
+</table>
+`;
diff --git a/server/sonar-web/src/main/js/apps/webhooks/routes.ts b/server/sonar-web/src/main/js/apps/webhooks/routes.ts
new file mode 100644
index 00000000000..fbd6fae2b97
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/webhooks/routes.ts
@@ -0,0 +1,32 @@
+/*
+ * 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 { RouterState, RouteComponent } from 'react-router';
+
+const routes = [
+ {
+ indexRoute: {
+ getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+ import('./components/App').then(i => callback(null, i.default));
+ }
+ }
+ }
+];
+
+export default routes;