@@ -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")); | |||
} | |||
@@ -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; | |||
} |
@@ -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} |
@@ -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> | |||
); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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> | |||
); | |||
} | |||
} |
@@ -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> | |||
</> | |||
); | |||
} |
@@ -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} | |||
/> | |||
)} | |||
</> | |||
); | |||
} | |||
} |
@@ -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} />); | |||
} |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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} /> | |||
); | |||
} |
@@ -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(); | |||
}); |
@@ -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(); | |||
}); |
@@ -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} |
@@ -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> | |||
`; |
@@ -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> | |||
`; |
@@ -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> | |||
`; |
@@ -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> | |||
`; |
@@ -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> | |||
`; |
@@ -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}> |
@@ -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. |