]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10346 Display webhook latest delivery status
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 9 Feb 2018 16:20:04 +0000 (17:20 +0100)
committerGuillaume Jambet <guillaume.jambet@gmail.com>
Thu, 1 Mar 2018 14:21:05 +0000 (15:21 +0100)
15 files changed:
server/sonar-web/src/main/js/apps/webhooks/components/App.tsx
server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx
server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx
server/sonar-web/src/main/js/apps/webhooks/components/PageActions.tsx
server/sonar-web/src/main/js/apps/webhooks/components/WebhookActions.tsx
server/sonar-web/src/main/js/apps/webhooks/components/WebhookItem.tsx
server/sonar-web/src/main/js/apps/webhooks/components/WebhooksList.tsx
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/App-test.tsx
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/PageActions-test.tsx
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookActions-test.tsx
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/WebhookItem-test.tsx
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhookActions-test.tsx.snap
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhookItem-test.tsx.snap
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/WebhooksList-test.tsx.snap
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 68a8f9d42bd66fef259430f1515d0e1acc5c1e80..9d33f1486e9fdc586253ae68ac17fc491c2ff5e4 100644 (file)
@@ -37,7 +37,7 @@ interface State {
 }
 
 export default class App extends React.PureComponent<Props, State> {
-  mounted: boolean = false;
+  mounted = false;
   state: State = { loading: true, webhooks: [] };
 
   componentDidMount() {
@@ -65,7 +65,11 @@ export default class App extends React.PureComponent<Props, State> {
   };
 
   getScopeParams = ({ organization, component } = this.props) => {
-    return { organization: organization && organization.key, project: component && component.key };
+    const organizationKey = organization && organization.key;
+    return {
+      organization: component ? component.organization : organizationKey,
+      project: component && component.key
+    };
   };
 
   handleCreate = (data: { name: string; url: string }) => {
index 33c0439d38e47d29ff860caf87d24c19a4f4a1a7..ce12280de2f182a5a1aaebf5a7c2be57c304fdfd 100644 (file)
@@ -40,7 +40,7 @@ interface State {
 const PAGE_SIZE = 10;
 
 export default class DeliveriesForm extends React.PureComponent<Props, State> {
-  mounted: boolean = false;
+  mounted = false;
   state: State = { deliveries: [], loading: true };
 
   componentDidMount() {
index 6eb15b3bc8bb2932cf91cafa8a79e2d1b68c15e1..cd75390ec0f4f01fce4f17d473d050ffc18fbfcf 100644 (file)
@@ -40,7 +40,7 @@ interface State {
 }
 
 export default class DeliveryItem extends React.PureComponent<Props, State> {
-  mounted: boolean = false;
+  mounted = false;
   state: State = { loading: false, open: false };
 
   componentDidMount() {
index 3968f8bdfbe6c9c6e03ca1a1e7a9517c50944c52..78e045ea959cddee54884efde801f2913b2de9b3 100644 (file)
@@ -35,7 +35,7 @@ interface State {
 const WEBHOOKS_LIMIT = 10;
 
 export default class PageActions extends React.PureComponent<Props, State> {
-  mounted: boolean = false;
+  mounted = false;
   state: State = { openCreate: false };
 
   componentDidMount() {
index 42351e01a1f0525b7cfa32177725ec525c239223..0027720ad60f4125a56b7f1941dc3f59e9d7841b 100644 (file)
@@ -40,7 +40,7 @@ interface State {
 }
 
 export default class WebhookActions extends React.PureComponent<Props, State> {
-  mounted: boolean = false;
+  mounted = false;
   state: State = { deliveries: false, updating: false };
 
   componentDidMount() {
@@ -77,18 +77,19 @@ export default class WebhookActions extends React.PureComponent<Props, State> {
 
   render() {
     const { webhook } = this.props;
-    // TODO Disable "Show deliveries" if there is no lastDelivery
     return (
       <>
         <ActionsDropdown className="big-spacer-left">
           <ActionsDropdownItem className="js-webhook-update" onClick={this.handleUpdateClick}>
             {translate('update_verb')}
           </ActionsDropdownItem>
-          <ActionsDropdownItem
-            className="js-webhook-deliveries"
-            onClick={this.handleDeliveriesClick}>
-            {translate('webhooks.deliveries.show')}
-          </ActionsDropdownItem>
+          {webhook.latestDelivery && (
+            <ActionsDropdownItem
+              className="js-webhook-deliveries"
+              onClick={this.handleDeliveriesClick}>
+              {translate('webhooks.deliveries.show')}
+            </ActionsDropdownItem>
+          )}
           <ActionsDropdownDivider />
           <ConfirmButton
             confirmButtonText={translate('delete')}
index b51ceb085af1157953a6114cbcf48609e3287863..5016508e13c88bfcf7b7e4525af667cc272c1140 100644 (file)
  */
 import * as React from 'react';
 import WebhookActions from './WebhookActions';
-import { Webhook } from '../../../app/types';
+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';
 
 interface Props {
   onDelete: (webhook: string) => Promise<void>;
@@ -32,9 +36,26 @@ export default function WebhookItem({ onDelete, onUpdate, webhook }: Props) {
     <tr>
       <td>{webhook.name}</td>
       <td>{webhook.url}</td>
+      <td>
+        <LatestDelivery latestDelivery={webhook.latestDelivery} />
+      </td>
       <td className="thin nowrap text-right">
         <WebhookActions onDelete={onDelete} onUpdate={onUpdate} webhook={webhook} />
       </td>
     </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>
+    </>
+  );
+}
index 57f33ffe151e1acc04a50bd427c83ebb47c2cd9b..92e8f191c293dc90f87db54e871b9143073477db 100644 (file)
@@ -35,6 +35,7 @@ export default class WebhooksList extends React.PureComponent<Props> {
       <tr>
         <th>{translate('name')}</th>
         <th>{translate('webhooks.url')}</th>
+        <th>{translate('webhooks.last_execution')}</th>
         <th />
       </tr>
     </thead>
index 62d45726d81eb5140395a3576cd819f227c7b80a..756f38308521a6e777df62add01b7586a4a44310 100644 (file)
@@ -81,7 +81,10 @@ describe('should correctly fetch webhooks when', () => {
     shallow(<App component={component} organization={undefined} />);
 
     await new Promise(setImmediate);
-    expect(searchWebhooks).toHaveBeenCalledWith({ project: component.key });
+    expect(searchWebhooks).toHaveBeenCalledWith({
+      project: component.key,
+      organization: component.organization
+    });
   });
 
   it('on organization scope', async () => {
index f50a9b062a4c177d67b146c05acfbbd0e536019a..e5a6e3741207d4f2b39f974ce4ba895cce55dc00 100644 (file)
@@ -47,5 +47,5 @@ it('should display the create form', () => {
 });
 
 function getWrapper(props = {}) {
-  return shallow(<PageActions onCreate={jest.fn()} loading={false} webhooksCount={5} {...props} />);
+  return shallow(<PageActions loading={false} onCreate={jest.fn()} webhooksCount={5} {...props} />);
 }
index b4331ce8ce81ca317ba97576d0ad6ee89ee3187b..70fe2bc5c77eb67e7c0bb2f0fa83adec878a7038 100644 (file)
@@ -22,7 +22,19 @@ import { shallow } from 'enzyme';
 import WebhookActions from '../WebhookActions';
 import { click } from '../../../../helpers/testUtils';
 
-const webhook = { key: '1', name: 'foo', url: 'http://foo.bar' };
+const webhook = {
+  key: '1',
+  name: 'foo',
+  url: 'http://foo.bar'
+};
+
+const delivery = {
+  at: '12.02.2018',
+  durationMs: 20,
+  httpStatus: 200,
+  id: '2',
+  success: true
+};
 
 it('should render correctly', () => {
   expect(getWrapper()).toMatchSnapshot();
@@ -55,7 +67,8 @@ it('should display the delete webhook form', () => {
 });
 
 it('should display the deliveries form', () => {
-  const wrapper = getWrapper();
+  const wrapper = getWrapper({ webhook: { ...webhook, latestDelivery: delivery } });
+  expect(wrapper).toMatchSnapshot();
   click(wrapper.find('.js-webhook-deliveries'));
   expect(wrapper.find('DeliveriesForm').exists()).toBeTruthy();
 });
index 9322cf58e0a4b562d55564b1b84997a6291a4066..6861daae1d2c135061ec3ba5c8eae8ced11e0704 100644 (file)
  */
 import * as React from 'react';
 import { shallow } from 'enzyme';
-import WebhookItem from '../WebhookItem';
+import WebhookItem, { LatestDelivery } from '../WebhookItem';
 
-const webhook = { key: '1', name: 'my webhook', url: 'http://webhook.target' };
+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', () => {
   expect(
@@ -34,3 +47,13 @@ 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();
+});
index e3d35952ba5cceaff7b7f50c24479d2a1598c811..7079ff776afb33e45941feb5555494fae8f1b892 100644 (file)
@@ -1,6 +1,6 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`should render correctly 1`] = `
+exports[`should display the deliveries form 1`] = `
 <React.Fragment>
   <ActionsDropdown
     className="big-spacer-left"
@@ -28,3 +28,26 @@ exports[`should render correctly 1`] = `
   </ActionsDropdown>
 </React.Fragment>
 `;
+
+exports[`should render correctly 1`] = `
+<React.Fragment>
+  <ActionsDropdown
+    className="big-spacer-left"
+  >
+    <ActionsDropdownItem
+      className="js-webhook-update"
+      onClick={[Function]}
+    >
+      update_verb
+    </ActionsDropdownItem>
+    <ActionsDropdownDivider />
+    <ConfirmButton
+      confirmButtonText="delete"
+      isDestructive={true}
+      modalBody="webhooks.delete.confirm.foo"
+      modalHeader="webhooks.delete"
+      onConfirm={[Function]}
+    />
+  </ActionsDropdown>
+</React.Fragment>
+`;
index 7f45d646e0ab1790fbb49aa59e488536f2be7fa2..99338c1907fe7fe8c8c20c8c682c2c6b940092d8 100644 (file)
@@ -8,6 +8,19 @@ exports[`should render correctly 1`] = `
   <td>
     http://webhook.target
   </td>
+  <td>
+    <LatestDelivery
+      latestDelivery={
+        Object {
+          "at": "12.02.2018",
+          "durationMs": 20,
+          "httpStatus": 200,
+          "id": "2",
+          "success": true,
+        }
+      }
+    />
+  </td>
   <td
     className="thin nowrap text-right"
   >
@@ -17,6 +30,13 @@ 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",
         }
@@ -25,3 +45,35 @@ 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>
+`;
index ff8401ea4612d8abd46939cfa610dbb797b19a69..1f4b340e35e1a89ded192871d1c0c71771d9dd35 100644 (file)
@@ -18,6 +18,9 @@ exports[`should correctly render the webhooks 1`] = `
       <th>
         webhooks.url
       </th>
+      <th>
+        webhooks.last_execution
+      </th>
       <th />
     </tr>
   </thead>
index daf129eab20862b8cfcae87f7e52430ed99fd412..f8b475cabd683fde23b0a1d6cfcdc1a763e64ef9 100644 (file)
@@ -2824,6 +2824,8 @@ webhooks.delivery.duration_x=Duration: {0}
 webhooks.delivery.payload=Payload:
 webhooks.delivery.response_x=Response: {0}
 webhooks.documentation_link=Webhooks documentation
+webhooks.last_execution=Last execution
+webhooks.last_execution.none=Never
 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.