import {Fragment} from 'react'; import {RouteComponentProps} from 'react-router'; import { addErrorMessage, addLoadingMessage, clearIndicators, } from 'sentry/actionCreators/indicator'; import {Button} from 'sentry/components/button'; import EmptyMessage from 'sentry/components/emptyMessage'; import FieldGroup from 'sentry/components/forms/fieldGroup'; import Link from 'sentry/components/links/link'; import {Panel, PanelAlert, PanelBody, PanelHeader} from 'sentry/components/panels'; import Switch from 'sentry/components/switchButton'; import Truncate from 'sentry/components/truncate'; import {IconAdd} from 'sentry/icons'; import {t} from 'sentry/locale'; import {Organization, ServiceHook} from 'sentry/types'; import withOrganization from 'sentry/utils/withOrganization'; import AsyncView from 'sentry/views/asyncView'; import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader'; type RowProps = { hook: ServiceHook; onToggleActive: () => void; orgId: string; projectId: string; }; function ServiceHookRow({orgId, projectId, hook, onToggleActive}: RowProps) { return ( <FieldGroup label={ <Link data-test-id="project-service-hook" to={`/settings/${orgId}/projects/${projectId}/hooks/${hook.id}/`} > <Truncate value={hook.url} /> </Link> } help={ <small> {hook.events && hook.events.length !== 0 ? ( hook.events.join(', ') ) : ( <em>{t('no events configured')}</em> )} </small> } > <Switch isActive={hook.status === 'active'} size="lg" toggle={onToggleActive} /> </FieldGroup> ); } type Props = RouteComponentProps<{projectId: string}, {}> & { organization: Organization; }; type State = { hookList: null | ServiceHook[]; } & AsyncView['state']; class ProjectServiceHooks extends AsyncView<Props, State> { getEndpoints(): ReturnType<AsyncView['getEndpoints']> { const {organization, params} = this.props; const projectId = params.projectId; return [['hookList', `/projects/${organization.slug}/${projectId}/hooks/`]]; } onToggleActive = (hook: ServiceHook) => { const {organization, params} = this.props; const {hookList} = this.state; if (!hookList) { return; } addLoadingMessage(t('Saving changes\u2026')); this.api.request( `/projects/${organization.slug}/${params.projectId}/hooks/${hook.id}/`, { method: 'PUT', data: { isActive: hook.status !== 'active', }, success: data => { clearIndicators(); this.setState({ hookList: hookList.map(h => { if (h.id === data.id) { return { ...h, ...data, }; } return h; }), }); }, error: () => { addErrorMessage(t('Unable to remove application. Please try again.')); }, } ); }; renderEmpty() { return ( <EmptyMessage> {t('There are no service hooks associated with this project.')} </EmptyMessage> ); } renderResults() { const {organization, params} = this.props; return ( <Fragment> <PanelHeader key="header">{t('Service Hook')}</PanelHeader> <PanelBody key="body"> <PanelAlert type="info" showIcon> {t( 'Service Hooks are an early adopter preview feature and will change in the future.' )} </PanelAlert> {this.state.hookList?.map(hook => ( <ServiceHookRow key={hook.id} orgId={organization.slug} projectId={params.projectId} hook={hook} onToggleActive={this.onToggleActive.bind(this, hook)} /> ))} </PanelBody> </Fragment> ); } renderBody() { const {hookList} = this.state; const body = hookList && hookList.length > 0 ? this.renderResults() : this.renderEmpty(); const {organization, params} = this.props; return ( <Fragment> <SettingsPageHeader title={t('Service Hooks')} action={ organization.access.includes('project:write') ? ( <Button data-test-id="new-service-hook" to={`/settings/${organization.slug}/projects/${params.projectId}/hooks/new/`} size="sm" priority="primary" icon={<IconAdd size="xs" isCircled />} > {t('Create New Hook')} </Button> ) : null } /> <Panel>{body}</Panel> </Fragment> ); } } export default withOrganization(ProjectServiceHooks);