Browse Source

SONAR-10346 Add a shortcut to display the latest delivery of a webhook

tags/7.5
Grégoire Aubert 6 years ago
parent
commit
06feeac0dc
21 changed files with 823 additions and 284 deletions
  1. 1
    1
      server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/WebhooksPage.java
  2. 9
    9
      server/sonar-web/src/main/js/app/types.ts
  3. 3
    2
      server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx
  4. 107
    0
      server/sonar-web/src/main/js/apps/webhooks/components/DeliveryAccordion.tsx
  5. 29
    85
      server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx
  6. 98
    0
      server/sonar-web/src/main/js/apps/webhooks/components/LatestDeliveryForm.tsx
  7. 3
    20
      server/sonar-web/src/main/js/apps/webhooks/components/WebhookItem.tsx
  8. 91
    0
      server/sonar-web/src/main/js/apps/webhooks/components/WebhookItemLatestDelivery.tsx
  9. 58
    0
      server/sonar-web/src/main/js/apps/webhooks/components/__tests__/DeliveryAccordion-test.tsx
  10. 16
    20
      server/sonar-web/src/main/js/apps/webhooks/components/__tests__/DeliveryItem-test.tsx
  11. 61
    0
      server/sonar-web/src/main/js/apps/webhooks/components/__tests__/LatestDeliveryForm-test.tsx
  12. 2
    21
      server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookItem-test.tsx
  13. 67
    0
      server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookItemLatestDelivery-test.tsx
  14. 3
    2
      server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/DeliveriesForm-test.tsx.snap
  15. 56
    0
      server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/DeliveryAccordion-test.tsx.snap
  16. 76
    74
      server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/DeliveryItem-test.tsx.snap
  17. 80
    0
      server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/LatestDeliveryForm-test.tsx.snap
  18. 5
    46
      server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhookItem-test.tsx.snap
  19. 49
    0
      server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhookItemLatestDelivery-test.tsx.snap
  20. 6
    3
      server/sonar-web/src/main/js/components/controls/ListFooter.tsx
  21. 3
    1
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 1
- 1
server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/WebhooksPage.java View File

@@ -92,7 +92,7 @@ public class WebhooksPage {
SelenideElement webhook = getWebhook(webhookName);
webhook.$(".dropdown-toggle").shouldBe(visible).click();
webhook.$(".js-webhook-deliveries").shouldBe(visible).click();
modalShouldBeOpen("Recent deliveries for " + webhookName);
modalShouldBeOpen("Recent deliveries of " + webhookName);
return new DeliveriesForm($(".modal-body"));
}


+ 9
- 9
server/sonar-web/src/main/js/app/types.ts View File

@@ -385,16 +385,16 @@ export enum Visibility {
}

export interface Webhook {
key: string;
latestDelivery?: WebhookDelivery;
name: string;
url: string;
key: string;
latestDelivery?: WebhookDelivery;
name: string;
url: string;
}

export interface WebhookDelivery {
at: string;
durationMs: number;
httpStatus: number;
id: string;
success: boolean;
at: string;
durationMs: number;
httpStatus?: number;
id: string;
success: boolean;
}

+ 3
- 2
server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx View File

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import DeliveryItem from './DeliveryItem';
import DeliveryAccordion from './DeliveryAccordion';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
import ListFooter from '../../../components/controls/ListFooter';
import Modal from '../../../components/controls/Modal';
@@ -96,12 +96,13 @@ export default class DeliveriesForm extends React.PureComponent<Props, State> {
<h2>{header}</h2>
</header>
<div className="modal-body modal-container">
{deliveries.map(delivery => <DeliveryItem delivery={delivery} key={delivery.id} />)}
{deliveries.map(delivery => <DeliveryAccordion delivery={delivery} key={delivery.id} />)}
<div className="text-center">
<DeferredSpinner loading={loading} />
</div>
{paging !== undefined && (
<ListFooter
className="little-spacer-bottom"
count={deliveries.length}
loadMore={this.fetchMoreDeliveries}
ready={!loading}

+ 107
- 0
server/sonar-web/src/main/js/apps/webhooks/components/DeliveryAccordion.tsx View File

@@ -0,0 +1,107 @@
/*
* 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 DeliveryItem from './DeliveryItem';
import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon';
import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import BoxedGroupAccordion from '../../../components/controls/BoxedGroupAccordion';
import { getDelivery } from '../../../api/webhooks';
import { WebhookDelivery } from '../../../app/types';

interface Props {
delivery: WebhookDelivery;
}

interface State {
loading: boolean;
open: boolean;
payload?: string;
}

export default class DeliveryAccordion extends React.PureComponent<Props, State> {
mounted = false;
state: State = { loading: false, open: false };

componentDidMount() {
this.mounted = true;
}

componentWillUnmount() {
this.mounted = false;
}

fetchPayload = ({ delivery } = this.props) => {
this.setState({ loading: true });
return getDelivery({ deliveryId: delivery.id }).then(
({ delivery }) => {
if (this.mounted) {
this.setState({ payload: delivery.payload, loading: false });
}
},
() => {
if (this.mounted) {
this.setState({ loading: false });
}
}
);
};

formatPayload = (payload: string) => {
try {
return JSON.stringify(JSON.parse(payload), undefined, 2);
} catch (error) {
return payload;
}
};

handleClick = () => {
if (!this.state.payload) {
this.fetchPayload();
}
this.setState(({ open }) => ({ open: !open }));
};

render() {
const { delivery } = this.props;
const { loading, open, payload } = this.state;

return (
<BoxedGroupAccordion
onClick={this.handleClick}
open={open}
renderHeader={() =>
delivery.success ? (
<AlertSuccessIcon className="pull-right js-success" />
) : (
<AlertErrorIcon className="pull-right js-error" />
)
}
title={<DateTimeFormatter date={delivery.at} />}>
<DeliveryItem
className="big-spacer-left"
delivery={delivery}
loading={loading}
payload={payload}
/>
</BoxedGroupAccordion>
);
}
}

+ 29
- 85
server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx View File

@@ -18,102 +18,46 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon';
import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import BoxedGroupAccordion from '../../../components/controls/BoxedGroupAccordion';
import CodeSnippet from '../../../components/common/CodeSnippet';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
import { getDelivery } from '../../../api/webhooks';
import { formatMeasure } from '../../../helpers/measures';
import { translateWithParameters, translate } from '../../../helpers/l10n';
import { WebhookDelivery } from '../../../app/types';

interface Props {
className?: string;
delivery: WebhookDelivery;
}

interface State {
loading: boolean;
open: boolean;
payload?: string;
payload: string | undefined;
}

export default class DeliveryItem extends React.PureComponent<Props, State> {
mounted = false;
state: State = { loading: false, open: false };

componentDidMount() {
this.mounted = true;
}

componentWillUnmount() {
this.mounted = false;
}

fetchPayload = ({ delivery } = this.props) => {
this.setState({ loading: true });
return getDelivery({ deliveryId: delivery.id }).then(
({ delivery }) => {
if (this.mounted) {
this.setState({ payload: delivery.payload, loading: false });
}
},
() => {
if (this.mounted) {
this.setState({ loading: false });
}
}
);
};

formatPayload = (payload: string) => {
try {
return JSON.stringify(JSON.parse(payload), undefined, 2);
} catch (error) {
return payload;
}
};

handleClick = () => {
if (!this.state.payload) {
this.fetchPayload();
}
this.setState(({ open }) => ({ open: !open }));
};

render() {
const { delivery } = this.props;
const { loading, open, payload } = this.state;
export default function DeliveryItem({ className, delivery, loading, payload }: Props) {
return (
<div className={className}>
<p className="spacer-bottom">
{translateWithParameters(
'webhooks.delivery.response_x',
delivery.httpStatus || translate('webhooks.delivery.server_unreachable')
)}
</p>
<p className="spacer-bottom">
{translateWithParameters(
'webhooks.delivery.duration_x',
formatMeasure(delivery.durationMs, 'MILLISEC')
)}
</p>
<p className="spacer-bottom">{translate('webhooks.delivery.payload')}</p>
<DeferredSpinner className="spacer-left spacer-top" loading={loading}>
{payload && <CodeSnippet noCopy={true} snippet={formatPayload(payload)} />}
</DeferredSpinner>
</div>
);
}

return (
<BoxedGroupAccordion
onClick={this.handleClick}
open={open}
renderHeader={() =>
delivery.success ? (
<AlertSuccessIcon className="pull-right js-success" />
) : (
<AlertErrorIcon className="pull-right js-error" />
)
}
title={<DateTimeFormatter date={delivery.at} />}>
<div className="big-spacer-left">
<p className="spacer-bottom">
{translateWithParameters('webhooks.delivery.response_x', delivery.httpStatus)}
</p>
<p className="spacer-bottom">
{translateWithParameters(
'webhooks.delivery.duration_x',
formatMeasure(delivery.durationMs, 'MILLISEC')
)}
</p>
<p className="spacer-bottom">{translate('webhooks.delivery.payload')}</p>
<DeferredSpinner className="spacer-left spacer-top" loading={loading}>
{payload && <CodeSnippet noCopy={true} snippet={this.formatPayload(payload)} />}
</DeferredSpinner>
</div>
</BoxedGroupAccordion>
);
function formatPayload(payload: string) {
try {
return JSON.stringify(JSON.parse(payload), undefined, 2);
} catch (error) {
return payload;
}
}

+ 98
- 0
server/sonar-web/src/main/js/apps/webhooks/components/LatestDeliveryForm.tsx View File

@@ -0,0 +1,98 @@
/*
* 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 DeliveryItem from './DeliveryItem';
import Modal from '../../../components/controls/Modal';
import { Webhook, WebhookDelivery } from '../../../app/types';
import { translateWithParameters, translate } from '../../../helpers/l10n';
import { getDelivery } from '../../../api/webhooks';

interface Props {
delivery: WebhookDelivery;
onClose: () => void;
webhook: Webhook;
}

interface State {
loading: boolean;
payload?: string;
}

export default class LatestDeliveryForm extends React.PureComponent<Props, State> {
mounted = false;
state: State = { loading: true };

componentDidMount() {
this.mounted = true;
this.fetchPayload();
}

componentWillUnmount() {
this.mounted = false;
}

fetchPayload = ({ delivery } = this.props) => {
return getDelivery({ deliveryId: delivery.id }).then(
({ delivery }) => {
if (this.mounted) {
this.setState({ payload: delivery.payload, loading: false });
}
},
() => {
if (this.mounted) {
this.setState({ loading: false });
}
}
);
};

formatPayload = (payload: string) => {
try {
return JSON.stringify(JSON.parse(payload), undefined, 2);
} catch (error) {
return payload;
}
};

render() {
const { delivery, webhook } = this.props;
const { loading, payload } = this.state;
const header = translateWithParameters('webhooks.latest_delivery_for_x', webhook.name);

return (
<Modal contentLabel={header} onRequestClose={this.props.onClose}>
<header className="modal-head">
<h2>{header}</h2>
</header>
<DeliveryItem
className="modal-body modal-container"
delivery={delivery}
loading={loading}
payload={payload}
/>
<footer className="modal-foot">
<button className="button-link js-modal-close" onClick={this.props.onClose} type="button">
{translate('close')}
</button>
</footer>
</Modal>
);
}
}

+ 3
- 20
server/sonar-web/src/main/js/apps/webhooks/components/WebhookItem.tsx View File

@@ -18,12 +18,9 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import WebhookItemLatestDelivery from './WebhookItemLatestDelivery';
import WebhookActions from './WebhookActions';
import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon';
import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import { Webhook, WebhookDelivery } from '../../../app/types';
import { translate } from '../../../helpers/l10n';
import { Webhook } from '../../../app/types';

interface Props {
onDelete: (webhook: string) => Promise<void>;
@@ -37,7 +34,7 @@ export default function WebhookItem({ onDelete, onUpdate, webhook }: Props) {
<td>{webhook.name}</td>
<td>{webhook.url}</td>
<td>
<LatestDelivery latestDelivery={webhook.latestDelivery} />
<WebhookItemLatestDelivery webhook={webhook} />
</td>
<td className="thin nowrap text-right">
<WebhookActions onDelete={onDelete} onUpdate={onUpdate} webhook={webhook} />
@@ -45,17 +42,3 @@ export default function WebhookItem({ onDelete, onUpdate, webhook }: Props) {
</tr>
);
}

export function LatestDelivery({ latestDelivery }: { latestDelivery?: WebhookDelivery }) {
if (!latestDelivery) {
return <span>{translate('webhooks.last_execution.none')}</span>;
}
return (
<>
{latestDelivery.success ? <AlertSuccessIcon /> : <AlertErrorIcon />}
<span className="spacer-left">
<DateTimeFormatter date={latestDelivery.at} />
</span>
</>
);
}

+ 91
- 0
server/sonar-web/src/main/js/apps/webhooks/components/WebhookItemLatestDelivery.tsx View File

@@ -0,0 +1,91 @@
/*
* 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 LatestDeliveryForm from './LatestDeliveryForm';
import BulletListIcon from '../../../components/icons-components/BulletListIcon';
import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon';
import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import { ButtonIcon } from '../../../components/ui/buttons';
import { Webhook } from '../../../app/types';
import { translate } from '../../../helpers/l10n';

interface Props {
webhook: Webhook;
}

interface State {
modal: boolean;
}

export default class WebhookItemLatestDelivery extends React.PureComponent<Props, State> {
mounted = false;
state: State = { modal: false };

componentDidMount() {
this.mounted = true;
}

componentWillUnmount() {
this.mounted = false;
}

handleClick = () => {
this.setState({ modal: true });
};

handleModalClose = () => {
if (this.mounted) {
this.setState({ modal: false });
}
};

render() {
const { webhook } = this.props;
if (!webhook.latestDelivery) {
return <span>{translate('webhooks.last_execution.none')}</span>;
}

const { modal } = this.state;
return (
<>
{webhook.latestDelivery.success ? (
<AlertSuccessIcon className="text-text-top" />
) : (
<AlertErrorIcon className="text-text-top" />
)}
<span className="spacer-left display-inline-flex-center">
<DateTimeFormatter date={webhook.latestDelivery.at} />
<ButtonIcon className="button-small little-spacer-left" onClick={this.handleClick}>
<BulletListIcon />
</ButtonIcon>
</span>

{modal && (
<LatestDeliveryForm
delivery={webhook.latestDelivery}
onClose={this.handleModalClose}
webhook={webhook}
/>
)}
</>
);
}
}

+ 58
- 0
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/DeliveryAccordion-test.tsx View File

@@ -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 { shallow } from 'enzyme';
import DeliveryAccordion from '../DeliveryAccordion';
import { getDelivery } from '../../../../api/webhooks';

jest.mock('../../../../api/webhooks', () => ({
getDelivery: jest.fn(() =>
Promise.resolve({
delivery: { payload: '{ "success": true }' }
})
)
}));

const delivery = {
at: '12.02.2018',
durationMs: 20,
httpStatus: 200,
id: '2',
success: true
};

beforeEach(() => {
(getDelivery as jest.Mock<any>).mockClear();
});

it('should render correctly', async () => {
const wrapper = getWrapper();
expect(wrapper).toMatchSnapshot();

wrapper.find('BoxedGroupAccordion').prop<Function>('onClick')();
await new Promise(setImmediate);
expect(getDelivery).lastCalledWith({ deliveryId: delivery.id });
wrapper.update();
expect(wrapper).toMatchSnapshot();
});

function getWrapper(props = {}) {
return shallow(<DeliveryAccordion delivery={delivery} {...props} />);
}

+ 16
- 20
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/DeliveryItem-test.tsx View File

@@ -20,15 +20,6 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import DeliveryItem from '../DeliveryItem';
import { getDelivery } from '../../../../api/webhooks';

jest.mock('../../../../api/webhooks', () => ({
getDelivery: jest.fn(() =>
Promise.resolve({
delivery: { payload: '{ "success": true }' }
})
)
}));

const delivery = {
at: '12.02.2018',
@@ -38,21 +29,26 @@ const delivery = {
success: true
};

beforeEach(() => {
(getDelivery as jest.Mock<any>).mockClear();
});

it('should render correctly', async () => {
it('should render correctly', () => {
const wrapper = getWrapper();
expect(wrapper).toMatchSnapshot();
});

wrapper.find('BoxedGroupAccordion').prop<Function>('onClick')();
await new Promise(setImmediate);
expect(getDelivery).lastCalledWith({ deliveryId: delivery.id });
wrapper.update();
expect(wrapper).toMatchSnapshot();
it('should render correctly when no payload', () => {
expect(getWrapper({ loading: true, payload: undefined })).toMatchSnapshot();
});

it('should render correctly when no http status', () => {
expect(getWrapper({ delivery: { ...delivery, httpStatus: undefined } })).toMatchSnapshot();
});

function getWrapper(props = {}) {
return shallow(<DeliveryItem delivery={delivery} {...props} />);
return shallow(
<DeliveryItem
delivery={delivery}
loading={false}
payload={'{ status: "SUCCESS" }'}
{...props}
/>
);
}

+ 61
- 0
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/LatestDeliveryForm-test.tsx View File

@@ -0,0 +1,61 @@
/*
* 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 LatestDeliveryForm from '../LatestDeliveryForm';
import { getDelivery } from '../../../../api/webhooks';

jest.mock('../../../../api/webhooks', () => ({
getDelivery: jest.fn(() =>
Promise.resolve({
delivery: { payload: '{ "success": true }' }
})
)
}));

const delivery = {
at: '12.02.2018',
durationMs: 20,
httpStatus: 200,
id: '2',
success: true
};

const webhook = { key: '1', name: 'foo', url: 'http://foo.bar' };

beforeEach(() => {
(getDelivery as jest.Mock<any>).mockClear();
});

it('should render correctly', async () => {
const wrapper = getWrapper();
expect(wrapper).toMatchSnapshot();

await new Promise(setImmediate);
expect(getDelivery).lastCalledWith({ deliveryId: delivery.id });
wrapper.update();
expect(wrapper).toMatchSnapshot();
});

function getWrapper(props = {}) {
return shallow(
<LatestDeliveryForm delivery={delivery} onClose={jest.fn()} webhook={webhook} {...props} />
);
}

+ 2
- 21
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookItem-test.tsx View File

@@ -19,21 +19,12 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import WebhookItem, { LatestDelivery } from '../WebhookItem';

const latestDelivery = {
at: '12.02.2018',
durationMs: 20,
httpStatus: 200,
id: '2',
success: true
};
import WebhookItem from '../WebhookItem';

const webhook = {
key: '1',
name: 'my webhook',
url: 'http://webhook.target',
latestDelivery
url: 'http://webhook.target'
};

it('should render correctly', () => {
@@ -47,13 +38,3 @@ it('should render correctly', () => {
)
).toMatchSnapshot();
});

it('should render correctly the latest delivery', () => {
expect(shallow(<LatestDelivery latestDelivery={undefined} />)).toMatchSnapshot();
expect(shallow(<LatestDelivery latestDelivery={latestDelivery} />)).toMatchSnapshot();
expect(
shallow(
<LatestDelivery latestDelivery={{ ...latestDelivery, httpStatus: 500, success: false }} />
)
).toMatchSnapshot();
});

+ 67
- 0
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookItemLatestDelivery-test.tsx View File

@@ -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 WebhookItemLatestDelivery from '../WebhookItemLatestDelivery';
import { click } from '../../../../helpers/testUtils';

const latestDelivery = {
at: '12.02.2018',
durationMs: 20,
httpStatus: 200,
id: '2',
success: true
};

const webhook = {
key: '1',
name: 'my webhook',
url: 'http://webhook.target',
latestDelivery
};

it('should render correctly a success delivery', () => {
expect(shallow(<WebhookItemLatestDelivery webhook={webhook} />)).toMatchSnapshot();
});

it('should render correctly when no latest delivery', () => {
expect(
shallow(<WebhookItemLatestDelivery webhook={{ ...webhook, latestDelivery: undefined }} />)
).toMatchSnapshot();
});

it('should render correctly a failed delivery', () => {
expect(
shallow(
<WebhookItemLatestDelivery
webhook={{
...webhook,
latestDelivery: { ...latestDelivery, httpStatus: 500, success: false }
}}
/>
)
).toMatchSnapshot();
});

it('should display the latest delivery form', () => {
const wrapper = shallow(<WebhookItemLatestDelivery webhook={webhook} />);
click(wrapper.find('ButtonIcon'));
expect(wrapper.find('LatestDeliveryForm').exists()).toBeTruthy();
});

+ 3
- 2
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/DeliveriesForm-test.tsx.snap View File

@@ -53,7 +53,7 @@ exports[`should render correctly 2`] = `
<div
className="modal-body modal-container"
>
<DeliveryItem
<DeliveryAccordion
delivery={
Object {
"at": "12.02.2018",
@@ -65,7 +65,7 @@ exports[`should render correctly 2`] = `
}
key="2"
/>
<DeliveryItem
<DeliveryAccordion
delivery={
Object {
"at": "11.02.2018",
@@ -86,6 +86,7 @@ exports[`should render correctly 2`] = `
/>
</div>
<ListFooter
className="little-spacer-bottom"
count={2}
loadMore={[Function]}
ready={true}

+ 56
- 0
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/DeliveryAccordion-test.tsx.snap View File

@@ -0,0 +1,56 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<BoxedGroupAccordion
onClick={[Function]}
open={false}
renderHeader={[Function]}
title={
<DateTimeFormatter
date="12.02.2018"
/>
}
>
<DeliveryItem
className="big-spacer-left"
delivery={
Object {
"at": "12.02.2018",
"durationMs": 20,
"httpStatus": 200,
"id": "2",
"success": true,
}
}
loading={false}
/>
</BoxedGroupAccordion>
`;

exports[`should render correctly 2`] = `
<BoxedGroupAccordion
onClick={[Function]}
open={true}
renderHeader={[Function]}
title={
<DateTimeFormatter
date="12.02.2018"
/>
}
>
<DeliveryItem
className="big-spacer-left"
delivery={
Object {
"at": "12.02.2018",
"durationMs": 20,
"httpStatus": 200,
"id": "2",
"success": true,
}
}
loading={false}
payload="{ \\"success\\": true }"
/>
</BoxedGroupAccordion>
`;

+ 76
- 74
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/DeliveryItem-test.tsx.snap View File

@@ -1,84 +1,86 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<BoxedGroupAccordion
onClick={[Function]}
open={false}
renderHeader={[Function]}
title={
<DateTimeFormatter
date="12.02.2018"
/>
}
>
<div
className="big-spacer-left"
<div>
<p
className="spacer-bottom"
>
webhooks.delivery.response_x.200
</p>
<p
className="spacer-bottom"
>
webhooks.delivery.duration_x.20ms
</p>
<p
className="spacer-bottom"
>
<p
className="spacer-bottom"
>
webhooks.delivery.response_x.200
</p>
<p
className="spacer-bottom"
>
webhooks.delivery.duration_x.20ms
</p>
<p
className="spacer-bottom"
>
webhooks.delivery.payload
</p>
<DeferredSpinner
className="spacer-left spacer-top"
loading={false}
timeout={100}
webhooks.delivery.payload
</p>
<DeferredSpinner
className="spacer-left spacer-top"
loading={false}
timeout={100}
>
<CodeSnippet
noCopy={true}
snippet="{ status: \\"SUCCESS\\" }"
/>
</div>
</BoxedGroupAccordion>
</DeferredSpinner>
</div>
`;

exports[`should render correctly 2`] = `
<BoxedGroupAccordion
onClick={[Function]}
open={true}
renderHeader={[Function]}
title={
<DateTimeFormatter
date="12.02.2018"
exports[`should render correctly when no http status 1`] = `
<div>
<p
className="spacer-bottom"
>
webhooks.delivery.response_x.webhooks.delivery.server_unreachable
</p>
<p
className="spacer-bottom"
>
webhooks.delivery.duration_x.20ms
</p>
<p
className="spacer-bottom"
>
webhooks.delivery.payload
</p>
<DeferredSpinner
className="spacer-left spacer-top"
loading={false}
timeout={100}
>
<CodeSnippet
noCopy={true}
snippet="{ status: \\"SUCCESS\\" }"
/>
}
>
<div
className="big-spacer-left"
</DeferredSpinner>
</div>
`;

exports[`should render correctly when no payload 1`] = `
<div>
<p
className="spacer-bottom"
>
webhooks.delivery.response_x.200
</p>
<p
className="spacer-bottom"
>
webhooks.delivery.duration_x.20ms
</p>
<p
className="spacer-bottom"
>
<p
className="spacer-bottom"
>
webhooks.delivery.response_x.200
</p>
<p
className="spacer-bottom"
>
webhooks.delivery.duration_x.20ms
</p>
<p
className="spacer-bottom"
>
webhooks.delivery.payload
</p>
<DeferredSpinner
className="spacer-left spacer-top"
loading={false}
timeout={100}
>
<CodeSnippet
noCopy={true}
snippet="{
\\"success\\": true
}"
/>
</DeferredSpinner>
</div>
</BoxedGroupAccordion>
webhooks.delivery.payload
</p>
<DeferredSpinner
className="spacer-left spacer-top"
loading={true}
timeout={100}
/>
</div>
`;

+ 80
- 0
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/LatestDeliveryForm-test.tsx.snap View File

@@ -0,0 +1,80 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<Modal
contentLabel="webhooks.latest_delivery_for_x.foo"
onRequestClose={[MockFunction]}
>
<header
className="modal-head"
>
<h2>
webhooks.latest_delivery_for_x.foo
</h2>
</header>
<DeliveryItem
className="modal-body modal-container"
delivery={
Object {
"at": "12.02.2018",
"durationMs": 20,
"httpStatus": 200,
"id": "2",
"success": true,
}
}
loading={true}
/>
<footer
className="modal-foot"
>
<button
className="button-link js-modal-close"
onClick={[MockFunction]}
type="button"
>
close
</button>
</footer>
</Modal>
`;

exports[`should render correctly 2`] = `
<Modal
contentLabel="webhooks.latest_delivery_for_x.foo"
onRequestClose={[MockFunction]}
>
<header
className="modal-head"
>
<h2>
webhooks.latest_delivery_for_x.foo
</h2>
</header>
<DeliveryItem
className="modal-body modal-container"
delivery={
Object {
"at": "12.02.2018",
"durationMs": 20,
"httpStatus": 200,
"id": "2",
"success": true,
}
}
loading={false}
payload="{ \\"success\\": true }"
/>
<footer
className="modal-foot"
>
<button
className="button-link js-modal-close"
onClick={[MockFunction]}
type="button"
>
close
</button>
</footer>
</Modal>
`;

+ 5
- 46
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhookItem-test.tsx.snap View File

@@ -9,14 +9,12 @@ exports[`should render correctly 1`] = `
http://webhook.target
</td>
<td>
<LatestDelivery
latestDelivery={
<WebhookItemLatestDelivery
webhook={
Object {
"at": "12.02.2018",
"durationMs": 20,
"httpStatus": 200,
"id": "2",
"success": true,
"key": "1",
"name": "my webhook",
"url": "http://webhook.target",
}
}
/>
@@ -30,13 +28,6 @@ exports[`should render correctly 1`] = `
webhook={
Object {
"key": "1",
"latestDelivery": Object {
"at": "12.02.2018",
"durationMs": 20,
"httpStatus": 200,
"id": "2",
"success": true,
},
"name": "my webhook",
"url": "http://webhook.target",
}
@@ -45,35 +36,3 @@ exports[`should render correctly 1`] = `
</td>
</tr>
`;

exports[`should render correctly the latest delivery 1`] = `
<span>
webhooks.last_execution.none
</span>
`;

exports[`should render correctly the latest delivery 2`] = `
<React.Fragment>
<AlertSuccessIcon />
<span
className="spacer-left"
>
<DateTimeFormatter
date="12.02.2018"
/>
</span>
</React.Fragment>
`;

exports[`should render correctly the latest delivery 3`] = `
<React.Fragment>
<AlertErrorIcon />
<span
className="spacer-left"
>
<DateTimeFormatter
date="12.02.2018"
/>
</span>
</React.Fragment>
`;

+ 49
- 0
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhookItemLatestDelivery-test.tsx.snap View File

@@ -0,0 +1,49 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly a failed delivery 1`] = `
<React.Fragment>
<AlertErrorIcon
className="text-text-top"
/>
<span
className="spacer-left display-inline-flex-center"
>
<DateTimeFormatter
date="12.02.2018"
/>
<ButtonIcon
className="button-small little-spacer-left"
onClick={[Function]}
>
<BulletListIcon />
</ButtonIcon>
</span>
</React.Fragment>
`;

exports[`should render correctly a success delivery 1`] = `
<React.Fragment>
<AlertSuccessIcon
className="text-text-top"
/>
<span
className="spacer-left display-inline-flex-center"
>
<DateTimeFormatter
date="12.02.2018"
/>
<ButtonIcon
className="button-small little-spacer-left"
onClick={[Function]}
>
<BulletListIcon />
</ButtonIcon>
</span>
</React.Fragment>
`;

exports[`should render correctly when no latest delivery 1`] = `
<span>
webhooks.last_execution.none
</span>
`;

+ 6
- 3
server/sonar-web/src/main/js/components/controls/ListFooter.tsx View File

@@ -24,6 +24,7 @@ import { formatMeasure } from '../../helpers/measures';

interface Props {
count: number;
className?: string;
loadMore?: () => void;
ready?: boolean;
total: number;
@@ -44,9 +45,11 @@ export default function ListFooter({ ready = true, ...props }: Props) {
{translate('show_more')}
</a>
);
const className = classNames('spacer-top note text-center', {
'new-loading': !ready
});
const className = classNames(
'spacer-top note text-center',
{ 'new-loading': !ready },
props.className
);

return (
<footer className={className}>

+ 3
- 1
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -2819,13 +2819,15 @@ 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.deliveries.show=Show recent deliveries
webhooks.deliveries_for_x=Recent deliveries for {0}
webhooks.deliveries_for_x=Recent deliveries of {0}
webhooks.delivery.duration_x=Duration: {0}
webhooks.delivery.payload=Payload:
webhooks.delivery.response_x=Response: {0}
webhooks.delivery.server_unreachable=Server Unreachable
webhooks.documentation_link=Webhooks documentation
webhooks.last_execution=Last delivery
webhooks.last_execution.none=Never
webhooks.latest_delivery_for_x=Last delivery of {0}
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.

Loading…
Cancel
Save