From 9ccb477f29179fad4312aeb5cd35cfb769a41668 Mon Sep 17 00:00:00 2001 From: 7PH Date: Fri, 14 Jul 2023 14:28:27 +0200 Subject: [PATCH] SONAR-18447 Refactor webhook page code & Remove unreachable code --- .../js/apps/webhooks/__tests__/utils-test.ts | 29 +++ .../main/js/apps/webhooks/components/App.tsx | 184 +++++++----------- .../webhooks/components/CreateWebhookForm.tsx | 172 ++++++++-------- .../webhooks/components/DeliveriesForm.tsx | 140 ++++++------- .../webhooks/components/DeliveryAccordion.tsx | 107 ++++------ .../apps/webhooks/components/DeliveryItem.tsx | 9 +- .../components/LatestDeliveryForm.tsx | 90 +++------ .../apps/webhooks/components/PageActions.tsx | 68 +++---- .../components/UpdateWebhookSecretField.tsx | 32 +-- .../webhooks/components/WebhookActions.tsx | 145 +++++--------- .../components/WebhookItemLatestDelivery.tsx | 88 +++------ .../apps/webhooks/components/WebhooksList.tsx | 60 +++--- .../webhooks/components/__tests__/App-it.tsx | 4 +- .../src/main/js/apps/webhooks/utils.ts | 26 +++ 14 files changed, 492 insertions(+), 662 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/webhooks/__tests__/utils-test.ts create mode 100644 server/sonar-web/src/main/js/apps/webhooks/utils.ts diff --git a/server/sonar-web/src/main/js/apps/webhooks/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/webhooks/__tests__/utils-test.ts new file mode 100644 index 00000000000..44a6e88744e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/webhooks/__tests__/utils-test.ts @@ -0,0 +1,29 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { formatPayload } from '../utils'; + +it('should format payload properly', () => { + expect(formatPayload(`{ "foo": { "bar": 13 } }`)).toEqual(`{ + "foo": { + "bar": 13 + } +}`); + expect(formatPayload('not a JSON string')).toEqual('not a JSON string'); +}); diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx index 3c02424500c..cb046dfdd0a 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { Helmet } from 'react-helmet-async'; import { createWebhook, deleteWebhook, searchWebhooks, updateWebhook } from '../../../api/webhooks'; import withComponentContext from '../../../app/components/componentContext/withComponentContext'; @@ -29,131 +30,90 @@ import PageActions from './PageActions'; import PageHeader from './PageHeader'; import WebhooksList from './WebhooksList'; -interface Props { - // eslint-disable-next-line react/no-unused-prop-types +export interface AppProps { component?: Component; } -interface State { - loading: boolean; - webhooks: WebhookResponse[]; -} - -export class App extends React.PureComponent { - mounted = false; - state: State = { loading: true, webhooks: [] }; - - componentDidMount() { - this.mounted = true; - this.fetchWebhooks(); - } - - componentWillUnmount() { - this.mounted = false; - } - - fetchWebhooks = () => { - return searchWebhooks(this.getScopeParams()).then( - ({ webhooks }) => { - if (this.mounted) { - this.setState({ loading: false, webhooks }); - } - }, - () => { - if (this.mounted) { - this.setState({ loading: false }); - } - } - ); - }; - - getScopeParams = ({ component } = this.props) => { - return { - project: component && component.key, - }; - }; - - handleCreate = (data: { name: string; secret?: string; url: string }) => { +export function App({ component }: AppProps) { + const [loading, setLoading] = useState(true); + const [webhooks, setWebhooks] = useState([]); + + const getScopeParams = useCallback( + () => ({ + project: component?.key, + }), + [component?.key] + ); + + const fetchWebhooks = useCallback(async () => { + try { + const { webhooks } = await searchWebhooks(getScopeParams()); + setWebhooks(webhooks); + } finally { + setLoading(false); + } + }, [getScopeParams]); + + useEffect(() => { + fetchWebhooks(); + }, [fetchWebhooks]); + + async function handleCreate(data: { name: string; secret?: string; url: string }) { const createData = { name: data.name, url: data.url, ...(data.secret && { secret: data.secret }), - ...this.getScopeParams(), + ...getScopeParams(), }; - return createWebhook(createData).then(({ webhook }) => { - if (this.mounted) { - this.setState(({ webhooks }) => ({ webhooks: [...webhooks, webhook] })); - } - }); - }; - - handleDelete = (webhook: string) => { - return deleteWebhook({ webhook }).then(() => { - if (this.mounted) { - this.setState(({ webhooks }) => ({ - webhooks: webhooks.filter((item) => item.key !== webhook), - })); - } - }); - }; - - handleUpdate = (data: { webhook: string; name: string; secret?: string; url: string }) => { - const updateData = { - webhook: data.webhook, - name: data.name, - url: data.url, - secret: data.secret, - }; - - return updateWebhook(updateData).then(() => { - if (this.mounted) { - this.setState(({ webhooks }) => ({ - webhooks: webhooks.map((webhook) => - webhook.key === data.webhook - ? { - ...webhook, - name: data.name, - hasSecret: data.secret === undefined ? webhook.hasSecret : Boolean(data.secret), - url: data.url, - } - : webhook - ), - })); - } - }); - }; - - render() { - const { loading, webhooks } = this.state; - - return ( - <> - - + const { webhook } = await createWebhook(createData); + setWebhooks([...webhooks, webhook]); + } -
- - - + async function handleDelete(webhook: string) { + await deleteWebhook({ webhook }); + setWebhooks((webhooks) => webhooks.filter((item) => item.key !== webhook)); + } - {!loading && ( -
- -
- )} -
- + async function handleUpdate(data: { + webhook: string; + name: string; + secret?: string; + url: string; + }) { + await updateWebhook({ ...data }); + setWebhooks((webhooks) => + webhooks.map((webhook) => + webhook.key === data.webhook + ? { + ...webhook, + name: data.name, + hasSecret: data.secret === undefined ? webhook.hasSecret : Boolean(data.secret), + url: data.url, + } + : webhook + ) ); } + + return ( + <> + + + +
+ + + + + {!loading && ( +
+ +
+ )} +
+ + ); } export default withComponentContext(App); diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx index a2d8d0938b1..d2f0a8fa867 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx @@ -33,14 +33,12 @@ interface Props { webhook?: WebhookResponse; } -export default class CreateWebhookForm extends React.PureComponent { - handleCancelClick = (event: React.SyntheticEvent) => { - event.preventDefault(); - event.currentTarget.blur(); - this.props.onClose(); - }; +export default function CreateWebhookForm({ webhook, onClose, onDone }: Props) { + const isUpdate = !!webhook; + const modalHeader = isUpdate ? translate('webhooks.update') : translate('webhooks.create'); + const confirmButtonText = isUpdate ? translate('update_verb') : translate('create'); - handleValidate = (data: WebhookBasePayload) => { + function handleValidate(data: WebhookBasePayload) { const { name, secret, url } = data; const errors: { name?: string; secret?: string; url?: string } = {}; if (!name.trim()) { @@ -57,89 +55,83 @@ export default class CreateWebhookForm extends React.PureComponent { errors.secret = translate('webhooks.secret.bad_format'); } return errors; - }; + } - render() { - const { webhook } = this.props; - const isUpdate = !!webhook; - const modalHeader = isUpdate ? translate('webhooks.update') : translate('webhooks.create'); - const confirmButtonText = isUpdate ? translate('update_verb') : translate('create'); - return ( - - {({ dirty, errors, handleBlur, handleChange, isSubmitting, touched, values }) => ( - <> - + return ( + + {({ dirty, errors, handleBlur, handleChange, isSubmitting, touched, values }) => ( + <> + - - {translate('webhooks.name')} - - - } - name="name" - onBlur={handleBlur} - onChange={handleChange} - touched={touched.name} - type="text" - value={values.name} - /> - - {translate('webhooks.url')} - - - } - name="url" - onBlur={handleBlur} - onChange={handleChange} - touched={touched.url} - type="text" - value={values.url} - /> - {translate('webhooks.secret')}} - name="secret" - onBlur={handleBlur} - onChange={handleChange} - touched={touched.secret} - type="password" - value={values.secret} - /> - - )} - - ); - } + + {translate('webhooks.name')} + + + } + name="name" + onBlur={handleBlur} + onChange={handleChange} + touched={touched.name} + type="text" + value={values.name} + /> + + {translate('webhooks.url')} + + + } + name="url" + onBlur={handleBlur} + onChange={handleChange} + touched={touched.url} + type="text" + value={values.url} + /> + {translate('webhooks.secret')}} + name="secret" + onBlur={handleBlur} + onChange={handleChange} + touched={touched.secret} + type="password" + value={values.secret} + /> + + )} + + ); } diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx index 85e70128724..e3c3dd82e32 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { searchDeliveries } from '../../../api/webhooks'; import ListFooter from '../../../components/controls/ListFooter'; import Modal from '../../../components/controls/Modal'; @@ -33,93 +34,74 @@ interface Props { webhook: WebhookResponse; } -interface State { - deliveries: WebhookDelivery[]; - loading: boolean; - paging?: Paging; -} - const PAGE_SIZE = 10; -export default class DeliveriesForm extends React.PureComponent { - mounted = false; - state: State = { deliveries: [], loading: true }; +export default function DeliveriesForm({ onClose, webhook }: Props) { + const [deliveries, setDeliveries] = useState([]); + const [loading, setLoading] = useState(true); + const [paging, setPaging] = useState(undefined); - componentDidMount() { - this.mounted = true; - this.fetchDeliveries(); - } + const header = translateWithParameters('webhooks.deliveries_for_x', webhook.name); - componentWillUnmount() { - this.mounted = false; - } + const fetchDeliveries = useCallback(async () => { + try { + const { deliveries, paging } = await searchDeliveries({ + webhook: webhook.key, + ps: PAGE_SIZE, + }); + setDeliveries(deliveries); + setPaging(paging); + } finally { + setLoading(false); + } + }, [webhook.key]); - fetchDeliveries = ({ webhook } = this.props) => { - searchDeliveries({ webhook: webhook.key, ps: PAGE_SIZE }).then(({ deliveries, paging }) => { - if (this.mounted) { - this.setState({ deliveries, loading: false, paging }); - } - }, this.stopLoading); - }; + useEffect(() => { + fetchDeliveries(); + }, [fetchDeliveries]); - fetchMoreDeliveries = ({ webhook } = this.props) => { - const { paging } = this.state; + async function fetchMoreDeliveries() { if (paging) { - this.setState({ loading: true }); - searchDeliveries({ webhook: webhook.key, p: paging.pageIndex + 1, ps: PAGE_SIZE }).then( - ({ deliveries, paging }) => { - if (this.mounted) { - this.setState((state: State) => ({ - deliveries: [...state.deliveries, ...deliveries], - loading: false, - paging, - })); - } - }, - this.stopLoading - ); - } - }; - - stopLoading = () => { - if (this.mounted) { - this.setState({ loading: false }); + setLoading(true); + try { + const response = await searchDeliveries({ + webhook: webhook.key, + p: paging.pageIndex + 1, + ps: PAGE_SIZE, + }); + setDeliveries((deliveries) => [...deliveries, ...response.deliveries]); + setPaging(response.paging); + } finally { + setLoading(false); + } } - }; - - render() { - const { webhook } = this.props; - const { deliveries, loading, paging } = this.state; - const header = translateWithParameters('webhooks.deliveries_for_x', webhook.name); + } - return ( - -
-

{header}

-
-
- {deliveries.map((delivery) => ( - - ))} -
- -
- {paging !== undefined && ( - - )} + return ( + +
+

{header}

+
+
+ {deliveries.map((delivery) => ( + + ))} +
+
-
- - {translate('close')} - -
- - ); - } + {paging !== undefined && ( + + )} +
+
+ {translate('close')} +
+
+ ); } diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryAccordion.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryAccordion.tsx index 2fa68d95077..e74dd883905 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryAccordion.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryAccordion.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { useState } from 'react'; import { getDelivery } from '../../../api/webhooks'; import BoxedGroupAccordion from '../../../components/controls/BoxedGroupAccordion'; import AlertErrorIcon from '../../../components/icons/AlertErrorIcon'; @@ -31,79 +32,47 @@ interface Props { delivery: WebhookDelivery; } -interface State { - loading: boolean; - open: boolean; - payload?: string; -} - -export default class DeliveryAccordion extends React.PureComponent { - mounted = false; - state: State = { loading: false, open: false }; - - componentDidMount() { - this.mounted = true; - } +export default function DeliveryAccordion({ delivery }: Props) { + const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); + const [payload, setPayload] = useState(undefined); - 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) => { + async function fetchPayload() { + setLoading(true); try { - return JSON.stringify(JSON.parse(payload), undefined, 2); - } catch (error) { - return payload; + const response = await getDelivery({ deliveryId: delivery.id }); + setPayload(response.delivery.payload); + } finally { + setLoading(false); } - }; + } - handleClick = () => { - if (!this.state.payload) { - this.fetchPayload(); + function handleClick() { + if (!payload) { + fetchPayload(); } - this.setState(({ open }) => ({ open: !open })); - }; - - render() { - const { delivery } = this.props; - const { loading, open, payload } = this.state; - - return ( - - delivery.success ? ( - - ) : ( - - ) - } - title={} - > - - - ); + setOpen(!open); } + + return ( + + delivery.success ? ( + + ) : ( + + ) + } + title={} + > + + + ); } diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx index a3ae48c5508..cd184e8f9a5 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx @@ -23,6 +23,7 @@ import DeferredSpinner from '../../../components/ui/DeferredSpinner'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { formatMeasure } from '../../../helpers/measures'; import { WebhookDelivery } from '../../../types/webhook'; +import { formatPayload } from '../utils'; interface Props { className?: string; @@ -53,11 +54,3 @@ export default function DeliveryItem({ className, delivery, loading, payload }:
); } - -function formatPayload(payload: string) { - try { - return JSON.stringify(JSON.parse(payload), undefined, 2); - } catch (error) { - return payload; - } -} diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/LatestDeliveryForm.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/LatestDeliveryForm.tsx index ceedc15589c..45e8e29b578 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/LatestDeliveryForm.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/LatestDeliveryForm.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { getDelivery } from '../../../api/webhooks'; import Modal from '../../../components/controls/Modal'; import { ResetButtonLink } from '../../../components/controls/buttons'; @@ -31,69 +32,40 @@ interface Props { webhook: WebhookResponse; } -interface State { - loading: boolean; - payload?: string; -} - -export default class LatestDeliveryForm extends React.PureComponent { - mounted = false; - state: State = { loading: true }; - - componentDidMount() { - this.mounted = true; - this.fetchPayload(); - } - - componentWillUnmount() { - this.mounted = false; - } +export default function LatestDeliveryForm(props: Props) { + const { delivery, webhook, onClose } = props; + const [loading, setLoading] = useState(true); + const [payload, setPayload] = useState(undefined); - 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 }); - } - } - ); - }; + const header = translateWithParameters('webhooks.latest_delivery_for_x', webhook.name); - formatPayload = (payload: string) => { + const fetchPayload = useCallback(async () => { try { - return JSON.stringify(JSON.parse(payload), undefined, 2); - } catch (error) { - return payload; + const response = await getDelivery({ deliveryId: delivery.id }); + setPayload(response.delivery.payload); + } finally { + setLoading(false); } - }; + }, [delivery.id]); - render() { - const { delivery, webhook } = this.props; - const { loading, payload } = this.state; - const header = translateWithParameters('webhooks.latest_delivery_for_x', webhook.name); + useEffect(() => { + fetchPayload(); + }, [fetchPayload]); - return ( - -
-

{header}

-
- -
- - {translate('close')} - -
-
- ); - } + return ( + +
+

{header}

+
+ +
+ {translate('close')} +
+
+ ); } diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/PageActions.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/PageActions.tsx index 6bb66e3791d..a2195bdbe71 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/PageActions.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/PageActions.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { useState } from 'react'; import Tooltip from '../../../components/controls/Tooltip'; import { Button } from '../../../components/controls/buttons'; import { translate, translateWithParameters } from '../../../helpers/l10n'; @@ -29,62 +30,43 @@ interface Props { webhooksCount: number; } -interface State { - openCreate: boolean; -} - export const WEBHOOKS_LIMIT = 10; -export default class PageActions extends React.PureComponent { - mounted = false; - state: State = { openCreate: false }; +export default function PageActions(props: Props) { + const { loading, onCreate, webhooksCount } = props; - componentDidMount() { - this.mounted = true; - } + const [openCreate, setOpenCreate] = useState(false); - componentWillUnmount() { - this.mounted = false; + function handleCreateClose() { + setOpenCreate(false); } - handleCreateClose = () => { - if (this.mounted) { - this.setState({ openCreate: false }); - } - }; + function handleCreateOpen() { + setOpenCreate(true); + } - handleCreateOpen = () => { - this.setState({ openCreate: true }); - }; + if (loading) { + return null; + } - renderCreate = () => { - if (this.props.webhooksCount >= WEBHOOKS_LIMIT) { - return ( + if (webhooksCount >= WEBHOOKS_LIMIT) { + return ( +
- ); - } - - return ( - <> - - {this.state.openCreate && ( - - )} - +
); - }; - - render() { - if (this.props.loading) { - return null; - } - - return
{this.renderCreate()}
; } + + return ( +
+ + {openCreate && } +
+ ); } diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/UpdateWebhookSecretField.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/UpdateWebhookSecretField.tsx index 1d4b8def53f..a40af0d3994 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/UpdateWebhookSecretField.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/UpdateWebhookSecretField.tsx @@ -40,21 +40,23 @@ interface Props { value?: string; } -export default function UpdateWebhookSecretField({ - isUpdateForm, - description, - dirty, - disabled, - error, - id, - label, - name, - onBlur, - onChange, - touched, - type, - value, -}: Props) { +export default function UpdateWebhookSecretField(props: Props) { + const { + isUpdateForm, + description, + dirty, + disabled, + error, + id, + label, + name, + onBlur, + onChange, + touched, + type, + value, + } = props; + const [isSecretInputDisplayed, setIsSecretInputDisplayed] = React.useState(false); const [, , { setValue: setSecretValue }] = useField('secret'); diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/WebhookActions.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/WebhookActions.tsx index d8338c3cb37..30a6f174788 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/WebhookActions.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/WebhookActions.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { useState } from 'react'; import ActionsDropdown, { ActionsDropdownDivider, ActionsDropdownItem, @@ -34,107 +35,61 @@ interface Props { webhook: WebhookResponse; } -interface State { - deleting: boolean; - deliveries: boolean; - updating: boolean; -} - -export default class WebhookActions extends React.PureComponent { - mounted = false; - state: State = { deleting: false, deliveries: false, updating: false }; +export default function WebhookActions(props: Props) { + const { onDelete, onUpdate, webhook } = props; - componentDidMount() { - this.mounted = true; - } + const [deleting, setDeleting] = useState(false); + const [deliveries, setDeliveries] = useState(false); + const [updating, setUpdating] = useState(false); - componentWillUnmount() { - this.mounted = false; + function handleUpdate(data: { name: string; secret?: string; url: string }) { + return onUpdate({ ...data, webhook: webhook.key }); } - handleDelete = () => { - return this.props.onDelete(this.props.webhook.key); - }; - - handleDeleteClick = () => { - this.setState({ deleting: true }); - }; - - handleDeletingStop = () => { - if (this.mounted) { - this.setState({ deleting: false }); - } - }; - - handleDeliveriesClick = () => { - this.setState({ deliveries: true }); - }; - - handleDeliveriesStop = () => { - this.setState({ deliveries: false }); - }; - - handleUpdate = (data: { name: string; secret?: string; url: string }) => { - return this.props.onUpdate({ ...data, webhook: this.props.webhook.key }); - }; - - handleUpdateClick = () => { - this.setState({ updating: true }); - }; - - handleUpdatingStop = () => { - this.setState({ updating: false }); - }; - - render() { - const { webhook } = this.props; - return ( - <> - - - {translate('update_verb')} - - {webhook.latestDelivery && ( - - {translate('webhooks.deliveries.show')} - - )} - + return ( + <> + + setUpdating(true)}> + {translate('update_verb')} + + {webhook.latestDelivery && ( setDeliveries(true)} > - {translate('delete')} + {translate('webhooks.deliveries.show')} - - - {this.state.deliveries && ( - - )} - - {this.state.updating && ( - - )} - - {this.state.deleting && ( - )} - - ); - } + + setDeleting(true)} + > + {translate('delete')} + + + + {deliveries && setDeliveries(false)} webhook={webhook} />} + + {updating && ( + setUpdating(false)} + onDone={handleUpdate} + webhook={webhook} + /> + )} + + {deleting && ( + setDeleting(false)} + onSubmit={() => onDelete(webhook.key)} + webhook={webhook} + /> + )} + + ); } diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItemLatestDelivery.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItemLatestDelivery.tsx index d528dbf5e97..da0afe83a99 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItemLatestDelivery.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItemLatestDelivery.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { useState } from 'react'; import { ButtonIcon } from '../../../components/controls/buttons'; import AlertErrorIcon from '../../../components/icons/AlertErrorIcon'; import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon'; @@ -31,65 +32,38 @@ interface Props { webhook: WebhookResponse; } -interface State { - modal: boolean; -} - -export default class WebhookItemLatestDelivery extends React.PureComponent { - mounted = false; - state: State = { modal: false }; - - componentDidMount() { - this.mounted = true; - } +export default function WebhookItemLatestDelivery({ webhook }: Props) { + const [modalOpen, setModalOpen] = useState(false); - componentWillUnmount() { - this.mounted = false; + if (!webhook.latestDelivery) { + return {translate('webhooks.last_execution.none')}; } - handleClick = () => { - this.setState({ modal: true }); - }; + return ( + <> + {webhook.latestDelivery.success ? ( + + ) : ( + + )} + + + setModalOpen(true)} + > + + + - handleModalClose = () => { - if (this.mounted) { - this.setState({ modal: false }); - } - }; - - render() { - const { webhook } = this.props; - if (!webhook.latestDelivery) { - return {translate('webhooks.last_execution.none')}; - } - - const { modal } = this.state; - return ( - <> - {webhook.latestDelivery.success ? ( - - ) : ( - - )} - - - - - - - - {modal && ( - - )} - - ); - } + {modalOpen && ( + setModalOpen(false)} + webhook={webhook} + /> + )} + + ); } diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/WebhooksList.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/WebhooksList.tsx index b60aa9008c4..182e309aa57 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/WebhooksList.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/WebhooksList.tsx @@ -29,38 +29,32 @@ interface Props { webhooks: WebhookResponse[]; } -export default class WebhooksList extends React.PureComponent { - renderHeader = () => ( - - - {translate('name')} - {translate('webhooks.url')} - {translate('webhooks.secret_header')} - {translate('webhooks.last_execution')} - {translate('actions')} - - - ); - - render() { - const { webhooks } = this.props; - if (webhooks.length < 1) { - return

{translate('webhooks.no_result')}

; - } - return ( - - {this.renderHeader()} - - {sortBy(webhooks, (webhook) => webhook.name.toLowerCase()).map((webhook) => ( - - ))} - -
- ); +export default function WebhooksList({ webhooks, onDelete, onUpdate }: Props) { + if (webhooks.length < 1) { + return

{translate('webhooks.no_result')}

; } + + return ( + + + + + + + + + + + + {sortBy(webhooks, (webhook) => webhook.name.toLowerCase()).map((webhook) => ( + + ))} + +
{translate('name')}{translate('webhooks.url')}{translate('webhooks.secret_header')}{translate('webhooks.last_execution')}{translate('actions')}
+ ); } diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/App-it.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/App-it.tsx index f6af455509f..65b7f120e38 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/App-it.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/App-it.tsx @@ -25,7 +25,7 @@ import { mockComponent } from '../../../../helpers/mocks/component'; import { mockWebhook } from '../../../../helpers/mocks/webhook'; import { renderComponent } from '../../../../helpers/testReactTestingUtils'; import { byLabelText, byRole, byText } from '../../../../helpers/testSelector'; -import { App } from '../App'; +import { App, AppProps } from '../App'; import { WEBHOOKS_LIMIT } from '../PageActions'; const webhookService = new WebhooksMock(); @@ -248,7 +248,7 @@ describe('should properly show deliveries', () => { }); }); -function renderWebhooksApp(overrides: Partial = {}) { +function renderWebhooksApp(overrides: Partial = {}) { return renderComponent(); } diff --git a/server/sonar-web/src/main/js/apps/webhooks/utils.ts b/server/sonar-web/src/main/js/apps/webhooks/utils.ts new file mode 100644 index 00000000000..12b1d5090fb --- /dev/null +++ b/server/sonar-web/src/main/js/apps/webhooks/utils.ts @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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. + */ +export function formatPayload(payload: string) { + try { + return JSON.stringify(JSON.parse(payload), undefined, 2); + } catch (error) { + return payload; + } +} -- 2.39.5